-
Notifications
You must be signed in to change notification settings - Fork 522
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(terser): support directory inputs
- Loading branch information
Showing
9 changed files
with
196 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,76 @@ | ||
#!/usr/bin/env node | ||
/** | ||
* @fileoverview wraps the terser CLI to support minifying a directory | ||
* Terser doesn't support it; see https://github.com/terser/terser/issues/75 | ||
* TODO: maybe we should generalize this to a package which would be useful outside | ||
* bazel; however we would have to support the full terser CLI and not make | ||
* assumptions about how the argv looks. | ||
*/ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const child_process = require('child_process'); | ||
|
||
// Pass-through require, ensures that the nodejs_binary will load the version of terser | ||
// from @bazel/terser package.json, not some other version the user depends on. | ||
require('terser/bin/uglifyjs'); | ||
// Run Bazel with --define=VERBOSE_LOGS=1 to enable this logging | ||
const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS']; | ||
|
||
// TODO: add support for minifying multiple files (eg. a TreeArtifact) in a single execution | ||
// Under Node 12 it should use the worker threads API to saturate all local cores | ||
function log_verbose(...m) { | ||
if (VERBOSE_LOGS) console.error('[terser/index.js]', ...m); | ||
} | ||
|
||
// Peek at the arguments to find any directories declared as inputs | ||
let argv = process.argv.slice(2); | ||
// terser_minified.bzl always passes the inputs first, | ||
// then --output [out], then remaining args | ||
// We want to keep those remaining ones to pass to terser | ||
// Avoid a dependency on a library like minimist; keep it simple. | ||
const outputArgIndex = argv.findIndex((arg) => arg.startsWith('--')); | ||
|
||
const inputs = argv.slice(0, outputArgIndex); | ||
const output = argv[outputArgIndex + 1]; | ||
const residual = argv.slice(outputArgIndex + 2); | ||
|
||
log_verbose(`Running terser/index.js | ||
inputs: ${inputs} | ||
output: ${output} | ||
residual: ${residual}`); | ||
|
||
function isDirectory(input) { | ||
return fs.lstatSync(path.join(process.cwd(), input)).isDirectory(); | ||
} | ||
|
||
function terserDirectory(input) { | ||
if (!fs.existsSync(output)) { | ||
fs.mkdirSync(output); | ||
} | ||
|
||
fs.readdirSync(input).forEach(f => { | ||
if (f.endsWith('.js')) { | ||
const inputFile = path.join(input, path.basename(f)); | ||
const outputFile = path.join(output, path.basename(f)); | ||
// We don't want to implement a command-line parser for terser | ||
// so we invoke its CLI as child processes, just altering the input/output | ||
// arguments. See discussion: https://github.com/bazelbuild/rules_nodejs/issues/822 | ||
// FIXME: this causes unlimited concurrency, which will definitely eat all the RAM on your | ||
// machine; | ||
// we need to limit the concurrency with something like the p-limit package. | ||
// TODO: under Node 12 it should use the worker threads API to saturate all local cores | ||
child_process.fork( | ||
// __dirname will be the path to npm_bazel_terser (Bazel's name for our src/ directory) | ||
// and our node_modules are installed in the ../npm directory since they're external | ||
// Note that the fork API doesn't do any module resolution. | ||
path.join(__dirname, '../npm/node_modules/terser/bin/uglifyjs'), | ||
[inputFile, '--output', outputFile, ...residual]); | ||
} | ||
}); | ||
} | ||
|
||
if (!inputs.find(isDirectory)) { | ||
// Inputs were only files | ||
// Just use terser CLI exactly as it works outside bazel | ||
require('terser/bin/uglifyjs'); | ||
} else if (inputs.length > 1) { | ||
// We don't know how to merge multiple input dirs to one output dir | ||
throw new Error('terser_minified only allows a single input when minifying a directory'); | ||
} else { | ||
terserDirectory(inputs[0]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test") | ||
load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified") | ||
load(":rule.bzl", "declare_directory") | ||
|
||
# Check that filegroups work | ||
declare_directory( | ||
name = "dir", | ||
srcs = glob(["input*.js"]), | ||
) | ||
|
||
terser_minified( | ||
name = "out.min", | ||
# TODO: support sourcemaps too | ||
sourcemap = False, | ||
src_dir = "dir", | ||
) | ||
|
||
jasmine_node_test( | ||
name = "test", | ||
srcs = ["spec.js"], | ||
data = [":out.min"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
function a() { | ||
var somelongname = 'a'; | ||
console.log(somelongname); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
function b() { | ||
var someotherlongname = 'b'; | ||
console.log(someotherlongname); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
"Minimal test fixture to create a directory output" | ||
|
||
def _impl(ctx): | ||
dir = ctx.actions.declare_directory(ctx.label.name) | ||
ctx.actions.run_shell( | ||
inputs = ctx.files.srcs, | ||
outputs = [dir], | ||
# RBE requires that we mkdir, but outside RBE it might already exist | ||
command = "mkdir -p {0}; cp $@ {0}".format(dir.path), | ||
arguments = [s.path for s in ctx.files.srcs], | ||
) | ||
return [ | ||
DefaultInfo(files = depset([dir])), | ||
] | ||
|
||
declare_directory = rule(_impl, attrs = {"srcs": attr.label_list(allow_files = True)}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const fs = require('fs'); | ||
|
||
describe('terser on a directory', () => { | ||
it('should produce an output for each input', () => { | ||
expect(fs.existsSync(require.resolve(__dirname + '/out.min/input1.js'))).toBeTruthy(); | ||
expect(fs.existsSync(require.resolve(__dirname + '/out.min/input2.js'))).toBeTruthy(); | ||
}); | ||
}); |