alexmngn alexmngn - 2 months ago 11
Javascript Question

Uglify one JS file using requirejs optimizer

I'd like to know if there is a way to uglify only one file in command line using r.js (RequireJS Optimizer) which is already installed in my computer.
Like we can minify a css file using

node r.js cssIn="" out=""
...

I'm actually working from a computer without internet connection, I'm not able to install something else on it (especially using npm)

Maybe is there a possibility to download an uglifyjs package from another computer including all dependencies ready to be installed on mine? I didn't find something like that though...

Thanks

Answer

Copy uglify from another computer

Since you mentioned this as a possibility in your question, I'm going to cover it.

First method

  1. On the computer that has uglify-js. Find where uglify-js is installed. Archive that directory and its subdirectories.

  2. On the disconnected computer. Unarchive that directory in a node_modules subdirectory for your project. Let's suppose the project is in /home/johndoe/foo. I would want to unarchive it in /home/johndoe/foo/node_modules.

  3. You can use uglify-js at node_modules/uglify-js/bin/uglify-js.

This first method is the method I would use.

Second method

If somehow the first method does not work for you or does not work well, there's a second method. The fact is that copying installed Node packages does not perfectly replicate the installation process. In may cases it may work perfectly but in some cases it may not. So here's a method that makes npm go through the whole installation process.

  1. On the computer where uglify is installed, do:

    $ npm ls
    

    In the output look for uglify-js and all the packages that are inside it hierarchically. The output will look like this:

    ├── cookie@0.1.0
    ├─┬ uglify-js@2.4.12
    │ ├── async@0.2.10
    │ ├─┬ optimist@0.3.7
    │ │ └── wordwrap@0.0.2
    │ ├─┬ source-map@0.1.31
    │ │ └── amdefine@0.1.0
    │ └── uglify-to-browserify@1.0.2
    └── yaml@0.2.3
    

    In the output above you care about uglify-js up to and including uglify-to-browserify. For each of these packages, look into npm's cache for the compressed package file. On Linux and similar systems this will be in ~/.npm/. For instance, on my machine uglify-js is cached at ~/.npm/uglify-js/2.4.12/package.tgz. Copy each of these packages to media that you can take to the disconnected computer. Make sure that you copy the correct versions. They are listed after the @ sign in the listing above.

  2. Move the media to your disconnected computer.

  3. For each package install it with:

    $ npm install [path to the tgz file]
    

    When you pass a tgz to npm it ignores the network, so long as all dependencies are met. The order here is important. You want to start with packages that have no dependencies themselves. I beleive the way npm ls produces its list, if you just go in reverse order you'll be okay. So:

    • amdefine
    • source-map
    • wordrap
    • optimist
    • async
    • uglify-js

    At the end of this process the uglifyjs command will be in node_modules/.bin/uglifyjs. You can readily test it against one of uglifyjs' own files. For instance:

    $ node_modules/.bin/uglifyjs node_modules/uglify-js/lib/output.js
    

I've tried a few other methods but found that npm is quite eager to go to the network to find packages. It's really easy to think "maybe ABC would work..." and find that it does not because even though everything is available locally, npm still wants a network connection.

Using the uglify-js included in r.js

The r.js optimizer include s uglify-js in it. Unfortunately, it is not packaged to be trivially usable outside r.js. However, you can access it like this:

var r = require("./node_modules/.bin/r.js");
var fs = require("fs");

var config = {};

var fileContents = fs.readFileSync(process.argv[2]).toString();

r.tools.useLib("mine", function (require) {
    require(["uglifyjs/index"], function (uglify) {
        var parser = uglify.parser;
        var processor = uglify.uglify;

        var ast = parser.parse(fileContents, config.strict_semicolons);
        if (config.no_mangle !== true) {
            ast = processor.ast_mangle(ast, config);
        }
        ast = processor.ast_squeeze(ast, config);

        fileContents = processor.gen_code(ast, config);

        if (config.max_line_length) {
            fileContents = processor.split_lines(fileContents,
                                                 config.max_line_length);
        }

        //Add trailing semicolon to match uglifyjs command line version
        fileContents += ';';

        fs.writeFileSync(process.argv[3], fileContents);
    });
});

If the above code is saved in a file named script.js and invoked like this node script.js input output, it will uglify input and put the result in output. The key is to use the tools.useLib method exported by r.js. This allows access to the libraries that r.js includes. The rest of the code is mostly copied from the section in r.js where it performs uglification.

Use r.js at the command line

$ r.js -o name=main exclude=main out=built.js

This would optimize only the module main (in the file main.js) and put the output in build.js. It won't follow any dependencies of main. However, it modifies the define by adding the module's name as the first parameter. This could be removed with a post-processing step with sed, awk, perl, etc.

If the file you want to uglify would be shimmed for use with RequireJS (not an actual AMD-style module), you don't want to use the exclude parameter:

$ r.js -o name=shimmed out=built.js

Just like the first command above, this also does more than just uglify the file. It adds a define call at the end. Again, this could be taken out with sed or a similar tool.

(I've definitely been able to use these commands without baseUrl=.. However, depending on what you do exactly, r.js may complain. So you may have to add baseUrl=. to the command line.)

Comments