Skip to content

Commit

Permalink
Merge pull request #10 from egorkhmelev/master
Browse files Browse the repository at this point in the history
Fixed version of gem that calculate dependensies in proper way
  • Loading branch information
igrigorik committed Oct 12, 2012
2 parents e26b68f + 1df340c commit 43e9423
Show file tree
Hide file tree
Showing 17 changed files with 1,926 additions and 40 deletions.
28 changes: 12 additions & 16 deletions README.md
Expand Up @@ -9,16 +9,18 @@ If you want to use closure as your Javascript library in Rails 3, add this gem t
```ruby
gem 'closure-sprockets'
````
The gem ships with a Railtie which will automatically register a Closure preprocessor. From here, two more steps:
The gem ships with a Railtie which will automatically register a Closure preprocessor. From here, three more steps:

- [Download the latest version](http://code.google.com/closure/library/docs/gettingstarted.html) of closure library from Google and put it in `vendor/assets`
- [Download the latest version](http://code.google.com/closure/library/docs/gettingstarted.html) of closure library from Google and put it in `vendor/assets` or another appropriate folder
- Create `require_file.js` that will be your closure start point and include it with new directive `require_closure` at your `application.js`:
`//= require_closure require_file`
- Write some closure code!


### Javascripts

```js
// in one of your javascript files
// in your javascript `require_file.js` file or any depended file
goog.require('goog.dom');
function sayHello() {
Expand All @@ -29,7 +31,7 @@ function sayHello() {
window.onload = sayHello;
```

You can also add a `name.soy` template in your assets folder, and it will be automatically compiled to Javascript for you! Ex:
You can also add a `name.soy` template in your assets folder and require it by standard `require` directive, and it will be automatically compiled to Javascript for you! Ex:

```js
/** hello.soy */
Expand All @@ -45,6 +47,8 @@ You can also add a `name.soy` template in your assets folder, and it will be aut
```

```js
//= require examples/simple
var soy = goog.dom.createDom('h1', {'style': 'background-color:#EEE'}, examples.simple.helloSoy());
goog.dom.appendChild(document.body, soy);
```
Expand Down Expand Up @@ -88,14 +92,6 @@ body {
## Optional configuration
If you decided to put your `closure-library` directory somewhere other than `vendor/assets`, then you'll have to update your environment config with the right path:

```ruby
config.closure.lib = 'vendor/assets/path/to/closure-library'
```

## Using Closure Compressor for Minification
Closure also provides its own Javascript compressor. If you wish to use it, pull in the [closure-compiler](https://github.com/documentcloud/closure-compiler) gem:
Expand All @@ -113,11 +109,11 @@ config.assets.js_compressor = Closure::Compiler.new
If you are not using the closure compiler, then you may want to disable the dynamic deps.js loading. To do so, add the following snippet in `application.html.erb` above the javascript_include tag:
```html
<script type="text/javascript">
var CLOSURE_NO_DEPS = true;
</script>
<script type="text/javascript">
var CLOSURE_NO_DEPS = true;
</script>
```
### License
(MIT License) - Copyright (c) 2011 Ilya Grigorik
(MIT License) - Copyright &copy; 2011 Ilya Grigorik
256 changes: 256 additions & 0 deletions lib/bin/build/closurebuilder.py
@@ -0,0 +1,256 @@
#!/usr/bin/env python
#
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# 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.

"""Utility for Closure Library dependency calculation.
ClosureBuilder scans source files to build dependency info. From the
dependencies, the script can produce a deps.js file, a manifest in dependency
order, a concatenated script, or compiled output from the Closure Compiler.
Paths to files can be expressed as individual arguments to the tool (intended
for use with find and xargs). As a convenience, --root can be used to specify
all JS files below a directory.
usage: %prog [options] [file1.js file2.js ...]
"""

__author__ = 'nnaze@google.com (Nathan Naze)'


import logging
import optparse
import os
import sys

import depstree
import jscompiler
import source
import treescan


def _GetOptionsParser():
"""Get the options parser."""

parser = optparse.OptionParser(__doc__)
parser.add_option('-i',
'--input',
dest='inputs',
action='append',
default=[],
help='One or more input files to calculate dependencies '
'for. The namespaces in this file will be combined with '
'those given with the -n flag to form the set of '
'namespaces to find dependencies for.')
parser.add_option('-n',
'--namespace',
dest='namespaces',
action='append',
default=[],
help='One or more namespaces to calculate dependencies '
'for. These namespaces will be combined with those given '
'with the -i flag to form the set of namespaces to find '
'dependencies for. A Closure namespace is a '
'dot-delimited path expression declared with a call to '
'goog.provide() (e.g. "goog.array" or "foo.bar").')
parser.add_option('--root',
dest='roots',
action='append',
default=[],
help='The paths that should be traversed to build the '
'dependencies.')
parser.add_option('-o',
'--output_mode',
dest='output_mode',
type='choice',
action='store',
choices=['list', 'script', 'compiled'],
default='list',
help='The type of output to generate from this script. '
'Options are "list" for a list of filenames, "script" '
'for a single script containing the contents of all the '
'files, or "compiled" to produce compiled output with '
'the Closure Compiler. Default is "list".')
parser.add_option('-c',
'--compiler_jar',
dest='compiler_jar',
action='store',
help='The location of the Closure compiler .jar file.')
parser.add_option('-f',
'--compiler_flags',
dest='compiler_flags',
default=[],
action='append',
help='Additional flags to pass to the Closure compiler. '
'To pass multiple flags, --compiler_flags has to be '
'specified multiple times.')
parser.add_option('--output_file',
dest='output_file',
action='store',
help=('If specified, write output to this path instead of '
'writing to standard output.'))

return parser


def _GetInputByPath(path, sources):
"""Get the source identified by a path.
Args:
path: str, A path to a file that identifies a source.
sources: An iterable collection of source objects.
Returns:
The source from sources identified by path, if found. Converts to
absolute paths for comparison.
"""
for js_source in sources:
# Convert both to absolute paths for comparison.
if os.path.abspath(path) == os.path.abspath(js_source.GetPath()):
return js_source


def _GetClosureBaseFile(sources):
"""Given a set of sources, returns the one base.js file.
Note that if zero or two or more base.js files are found, an error message
will be written and the program will be exited.
Args:
sources: An iterable of _PathSource objects.
Returns:
The _PathSource representing the base Closure file.
"""
base_files = [
js_source for js_source in sources if _IsClosureBaseFile(js_source)]

if not base_files:
logging.error('No Closure base.js file found.')
sys.exit(1)
if len(base_files) > 1:
logging.error('More than one Closure base.js files found at these paths:')
for base_file in base_files:
logging.error(base_file.GetPath())
sys.exit(1)
return base_files[0]


def _IsClosureBaseFile(js_source):
"""Returns true if the given _PathSource is the Closure base.js source."""
return (os.path.basename(js_source.GetPath()) == 'base.js' and
js_source.provides == set(['goog']))


class _PathSource(source.Source):
"""Source file subclass that remembers its file path."""

def __init__(self, path):
"""Initialize a source.
Args:
path: str, Path to a JavaScript file. The source string will be read
from this file.
"""
super(_PathSource, self).__init__(source.GetFileContents(path))

self._path = path

def GetPath(self):
"""Returns the path."""
return self._path


def main():
logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
level=logging.INFO)
options, args = _GetOptionsParser().parse_args()

# Make our output pipe.
if options.output_file:
out = open(options.output_file, 'w')
else:
out = sys.stdout

sources = set()

logging.info('Scanning paths...')
for path in options.roots:
for js_path in treescan.ScanTreeForJsFiles(path):
sources.add(_PathSource(js_path))

# Add scripts specified on the command line.
for js_path in args:
sources.add(_PathSource(js_path))

logging.info('%s sources scanned.', len(sources))

# Though deps output doesn't need to query the tree, we still build it
# to validate dependencies.
logging.info('Building dependency tree..')
tree = depstree.DepsTree(sources)

input_namespaces = set()
inputs = options.inputs or []
for input_path in inputs:
js_input = _GetInputByPath(input_path, sources)
if not js_input:
logging.error('No source matched input %s', input_path)
sys.exit(1)
input_namespaces.update(js_input.provides)

input_namespaces.update(options.namespaces)

if not input_namespaces:
logging.error('No namespaces found. At least one namespace must be '
'specified with the --namespace or --input flags.')
sys.exit(2)

# The Closure Library base file must go first.
base = _GetClosureBaseFile(sources)
deps = [base] + tree.GetDependencies(input_namespaces)

output_mode = options.output_mode
if output_mode == 'list':
out.writelines([js_source.GetPath() + '\n' for js_source in deps])
elif output_mode == 'script':
out.writelines([js_source.GetSource() for js_source in deps])
elif output_mode == 'compiled':

# Make sure a .jar is specified.
if not options.compiler_jar:
logging.error('--compiler_jar flag must be specified if --output is '
'"compiled"')
sys.exit(2)

compiled_source = jscompiler.Compile(
options.compiler_jar,
[js_source.GetPath() for js_source in deps],
options.compiler_flags)

if compiled_source is None:
logging.error('JavaScript compilation failed.')
sys.exit(1)
else:
logging.info('JavaScript compilation succeeded.')
out.write(compiled_source)

else:
logging.error('Invalid value for --output flag.')
sys.exit(2)


if __name__ == '__main__':
main()

0 comments on commit 43e9423

Please sign in to comment.