Skip to content

Commit

Permalink
feat(terser): introduce @bazel/terser package
Browse files Browse the repository at this point in the history
This is a clean implementation starting from what we were running in the built-in rollup_bundle rule.
It doesn't yet incorporate our new JS Providers design; that will come next. For now it just understands immediate .js inputs
  • Loading branch information
alexeagle committed Aug 29, 2019
1 parent 48c5088 commit 232acfe
Show file tree
Hide file tree
Showing 40 changed files with 776 additions and 0 deletions.
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
'protractor',
'stylus',
'rollup',
'terser',
'typescript',
'worker',
]
Expand Down
1 change: 1 addition & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ _PACKAGE_READMES = {
"Less": "//packages/less:README.md",
"Protractor": "//packages/protractor:README.md",
"Stylus": "//packages/stylus:README.md",
"Terser": "//packages/terser:README.md",
"TypeScript": "//packages/typescript:README.md",
}

Expand Down
113 changes: 113 additions & 0 deletions docs/Terser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: Terser
layout: default
stylesheet: docs
---
# Terser rules for Bazel

**WARNING: this is beta-quality software. Breaking changes are likely. Not recommended for production use without expert support.**

The Terser rules run the Terser JS minifier with Bazel.

Wraps the Terser CLI documented at https://github.com/terser-js/terser#command-line-usage


## Installation

Add the `@bazel/terser` npm package to your `devDependencies` in `package.json`.

Your `WORKSPACE` should declare a `yarn_install` or `npm_install` rule named `npm`.
It should then install the rules found in the npm packages using the `install_bazel_dependencies` function.
See https://github.com/bazelbuild/rules_nodejs/#quickstart

This causes the `@bazel/terser` package to be installed as a Bazel workspace named `npm_bazel_terser`.


## Installing with self-managed dependencies

If you didn't use the `yarn_install` or `npm_install` rule to create an `npm` workspace, you'll have to declare a rule in your root `BUILD.bazel` file to execute terser:

```python
# Create a terser rule to use in terser_minified#terser_bin
# attribute when using self-managed dependencies
nodejs_binary(
name = "terser_bin",
entry_point = "//:node_modules/terser/bin/uglifyjs",
# Point bazel to your node_modules to find the entry point
node_modules = ["//:node_modules"],
)
```

[name]: https://bazel.build/docs/build-ref.html#name
[label]: https://bazel.build/docs/build-ref.html#labels
[labels]: https://bazel.build/docs/build-ref.html#labels


## terser_minified

Run the terser minifier.

Typical example:
```python
load("@npm_bazel_terser//:index.bzl", "terser_minified")

terser_minified(
name = "out.min",
src = "input.js",
config_file = "terser_config.json",
)
```

Note that the `name` attribute determines what the resulting files will be called.



### Usage

```
terser_minified(name, config_file, debug, sourcemap, src, terser_bin)
```



#### `name`
(*[name], mandatory*): A unique name for this target.


#### `config_file`
(*[label]*): A JSON file containing Terser minify() options.

This is the file you would pass to the --config-file argument in terser's CLI.
https://github.com/terser-js/terser#minify-options documents the content of the file.

Bazel will make a copy of your config file, treating it as a template.
If you use the magic strings `bazel_debug` or `bazel_no_debug`, these will be
replaced with `true` and `false` respecting the value of the `debug` attribute
or the `--define=DEBUG=true` bazel flag.

If this isn't supplied, Bazel will use a default config file.


#### `debug`
(*Boolean*): Configure terser to produce more readable output.

Instead of setting this attribute, consider setting the DEBUG variable instead
bazel build --define=DEBUG=true //my/terser:target
so that it only affects the current build.


#### `sourcemap`
(*Boolean*): Whether to produce a .js.map file for each .js output


#### `src`
(*[label], mandatory*): A JS file, or a rule producing .js as its default output

Note that you can pass multiple files to terser, which it will bundle together.
If you want to do this, you can pass a filegroup here.


#### `terser_bin`
(*[label]*): An executable target that runs Terser


6 changes: 6 additions & 0 deletions docs/_includes/drawer.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
<li><a href="Jasmine.html">Jasmine</a></li>
</ul>
</li>
<li>
<span class="drawer-nav-title">Optimizers</span>
<ul>
<li><a href="Terser.html">Terser</a></li>
</ul>
</li>
<li>
<span class="drawer-nav-title">Deployment</span>
<ul>
Expand Down
7 changes: 7 additions & 0 deletions e2e/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ e2e_integration_test(
},
)

e2e_integration_test(
name = "e2e_terser",
npm_packages = {
"//packages/terser:npm_package": "@bazel/terser",
},
)

e2e_integration_test(
name = "e2e_ts_devserver",
npm_packages = {
Expand Down
1 change: 1 addition & 0 deletions e2e/index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ALL_E2E = [
"stylus",
"symlinked_node_modules_npm",
"symlinked_node_modules_yarn",
"terser",
"ts_auto_deps",
"ts_devserver",
"typescript",
Expand Down
1 change: 1 addition & 0 deletions e2e/terser/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import %workspace%/../../common.bazelrc
27 changes: 27 additions & 0 deletions e2e/terser/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_test")
load("@npm_bazel_terser//:index.bzl", "terser_minified")

terser_minified(
name = "out.min",
src = "input.js",
)

nodejs_test(
name = "test",
data = ["out.min.js"],
entry_point = ":test.js",
)

# For testing from the root workspace of this repository with bazel_integration_test.
filegroup(
name = "all_files",
srcs = glob(
include = ["**/*"],
exclude = [
"bazel-out/**/*",
"dist/**/*",
"node_modules/**/*",
],
),
visibility = ["//visibility:public"],
)
38 changes: 38 additions & 0 deletions e2e/terser/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2018 The Bazel 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.

workspace(
name = "e2e_terser",
managed_directories = {"@npm": ["node_modules"]},
)

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "3356c6b767403392bab018ce91625f6d15ff8f11c6d772dc84bc9cada01c669a",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.36.1/rules_nodejs-0.36.1.tar.gz"],
)

load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install")

yarn_install(
name = "npm",
package_json = "//:package.json",
yarn_lock = "//:yarn.lock",
)

load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")

install_bazel_dependencies()
2 changes: 2 additions & 0 deletions e2e/terser/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const somelongname = 1;
console.error(somelongname);
10 changes: 10 additions & 0 deletions e2e/terser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "e2e-terser",
"private": true,
"devDependencies": {
"@bazel/terser": "latest"
},
"scripts": {
"test": "bazel test ..."
}
}
5 changes: 5 additions & 0 deletions e2e/terser/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const fs = require('fs');
const content = fs.readFileSync(require.resolve(__dirname + '/out.min.js'), 'utf-8');
if (content.indexOf('console.error(1)') < 1) {
console.error(content), process.exitCode = 1;
}
Empty file added e2e/terser/yarn.lock
Empty file.
3 changes: 3 additions & 0 deletions internal/golden_file_test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package(default_visibility = ["//visibility:public"])

exports_files(["bin.js"])
35 changes: 35 additions & 0 deletions internal/golden_file_test/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const fs = require('fs');
const path = require('path');
const unidiff = require('unidiff');

function main(args) {
const [mode, golden, actual] = args;
const actualContents = fs.readFileSync(require.resolve(actual), 'utf-8').replace(/\r\n/g, '\n');
const goldenContents = fs.readFileSync(require.resolve(golden), 'utf-8').replace(/\r\n/g, '\n');

if (actualContents !== goldenContents) {
if (mode === '--out') {
// Write to golden file
fs.writeFileSync(require.resolve(golden), actualContents);
console.error(`Replaced ${path.join(process.cwd(), golden)}`);
} else if (mode === '--verify') {
// Generated does not match golden
const diff = unidiff.diffLines(goldenContents, actualContents);
const prettyDiff = unidiff.formatLines(diff, {aname: golden, bname: actual});
throw new Error(`Actual output doesn't match golden file:
${prettyDiff}
Update the golden file:
bazel run ${process.env['BAZEL_TARGET'].replace(/_bin$/, '')}.accept
`);
} else {
throw new Error('unknown mode', mode);
}
}
}

if (require.main === module) {
main(process.argv.slice(2));
}
24 changes: 24 additions & 0 deletions internal/golden_file_test/golden_file_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"Convenience for testing that an output matches a file"

load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary", "nodejs_test")

def golden_file_test(name, golden, actual, **kwargs):
data = [golden, actual, "@npm//unidiff"]

loc = "$(location %s)"
nodejs_test(
name = name,
entry_point = "@build_bazel_rules_nodejs//internal/golden_file_test:bin.js",
templated_args = ["--verify", loc % golden, loc % actual],
data = data,
**kwargs
)

nodejs_binary(
name = name + ".accept",
testonly = True,
entry_point = "@build_bazel_rules_nodejs//internal/golden_file_test:bin.js",
templated_args = ["--out", loc % golden, loc % actual],
data = data,
**kwargs
)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"semver": "5.6.0",
"shelljs": "0.8.3",
"sinon": "^7.3.2",
"source-map": "^0.7.3",
"source-map-support": "0.5.9",
"stylus": "~0.54.5",
"terser": "3.17.0",
Expand Down
1 change: 1 addition & 0 deletions packages/index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ NESTED_PACKAGES = [
"less",
"protractor",
"stylus",
"terser",
"typescript",
]

Expand Down
42 changes: 42 additions & 0 deletions packages/terser/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2017 The Bazel 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.

load("@build_bazel_rules_nodejs//:tools/defaults.bzl", "npm_package")

# Ugly genrule depending on local linux environment to build the README out of skylark doc generation.
# Only referenced when we do a release.
# TODO: This ought to be possible with stardoc alone. Need to coordinate with Chris Parsons.
genrule(
name = "generate_README",
srcs = [
"//packages/terser/docs:index.md",
"//packages/terser/docs:install.md",
],
outs = ["README.md"],
cmd = """cat $(location //packages/terser/docs:install.md) $(location //packages/terser/docs:index.md) | sed 's/^##/\\\n##/' > $@""",
visibility = ["//docs:__pkg__"],
)

npm_package(
name = "npm_package",
srcs = [
"@npm_bazel_terser//:package_contents",
],
vendor_external = [
"npm_bazel_terser",
],
deps = [
":generate_README",
],
)
13 changes: 13 additions & 0 deletions packages/terser/docs/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//tools/stardoc:index.bzl", "stardoc")

package(default_visibility = ["//visibility:public"])

exports_files(["install.md"])

stardoc(
name = "docs",
testonly = True,
out = "index.md",
input = "@npm_bazel_terser//:index.bzl",
deps = ["@npm_bazel_terser//:bzl"],
)
Loading

0 comments on commit 232acfe

Please sign in to comment.