Any way to use goog.require in development with convention-over-configuration?

2.3k views Asked by At

Is there any way I can use Google Closure goog.require to manage JS dependencies, without having to register each namespace explicitly in a dependencies.js file?

I like the idea of the compiler for production, but for development, I'd like some kind of convention-over-configuration translation of namespace to JS folder/path such that something like goog.require('myapp.module') automatically imports myapp/module.js in development mode (if not already imported), while in production it all gets compiled into a single file.

I seem to remember old versions of dojo.require worked this way. Any idea if Google Closure can do the same thing?

3

There are 3 answers

1
Christopher Peisert On BEST ANSWER

Is there any way I can use Google Closure goog.require to manage JS dependencies, without having to register each namespace explicitly in a dependencies.js file?

The short answer is no. In uncompiled JavaScript code, goog.require() relies on a dependency graph generated by calls to goog.addDependency(relativePath, provides, requires).

From the DepsWriter documentation:

But how does Closure Library know which files provide which namespaces? Included in Closure Library is a default dependency file named deps.js. This file contains one line for every JavaScript file in the library, each specifying the file location (relative to base.js), the namespaces it provides, and the namespace it requires.

In debug mode, a goog.require() statement looks to see if the require namespace was specified and, if so, fetch the files that provide it and all of its dependencies.

However, Closure Library only comes with a dependency file for the namespaces in the library. If you plan to write your own namespaces (and you likely will), you can use depswriter.py to make a dependency file for the namespaces you create.

That being said, you could use a tool such as plovr, which provides a "RAW" mode to concatenate your JavaScript sources without compilation. This avoids the need to generate a deps file, but it also means that when debugging code in the browser, it is more challenging to determine the original file containing the code.

1
vasanth kumar On

The base.js file defines the function goog.require(). The function call goog.require('goog.dom') loads the JavaScript file that defines the functions in the goog.dom namespace, along with any other files from the Closure Library that those functions need.

3
Grant Robertson On

In the Closure tools there is a python script called calcdeps.py which generates exactly what you want.

Getting started with goog.provide/goog.require:

  • Organize your files into a directory structure to match the namespace(s) you desire. i.e. If you're declaring the namespace 'grantys.awesome.lib.Math', your directory structure should look something like grantys/awesome/lib/Math.js.

  • In each file you're adding as a dependency, the first javascript function call should be goog.provide('grantys.awesome.lib.Math'). To depend on another file or files, declare the requirement with goog.require('grantys.awesome.lib.Constants'). *this is important, goog.provide is what calcdeps.py uses to figure out what each file can provide -- uncompiled it makes absolutely zero guarantee that the namespace you say you're providing is what you're constructing in that file.

  • Run calcdeps.py with the proper arguments (paths to search for files which may or may not contain goog.provide('some.library'), the filename to write, etc). This will generate a file containing any goog.addDependency statements you'd otherwise have written manually. Also note, the deps file calcdeps generates uses relative paths, as seen from the directory in which you're writing the output file (not always the directory from which you run the command)

An example of the output of calcdeps.py:

goog.addDependency("../site/ui/MurphyBed.js", ['site.ui.MurphyBed', 'site.ui.MurphyBed.State', 'site.ui.MurphyBed.EventType', 'site.ui.MurphyBed.Error'], ['goog.dom', 'goog.object', 'goog.events', 'goog.events.EventTarget', 'site.fx.easing', 'goog.fx.dom', 'goog.fx.AnimationSerialQueue']);
goog.addDependency("../site/ui/SliderButton.js", ['site.ui.SliderButton'], ['goog.ui.CustomButton', 'site.ui.SliderButtonRenderer', 'goog.fx.dom', 'site.fx.easing']);
  • In your html, include google's base.js script. This declares the goog object. After the base.js script tag, add a tag which references your generated deps.js file you made using calcdeps.py above.

  • Load your dependencies using goog.require('grantys.awesome.lib.Math').

Note: If you change a dependency, you must rerun calcdeps.py. My own solution was to wrap it in a shell script with the correct args, so when I make a change that requires the files to be rescaned, I just run ./calcdeps.sh