forked from bazel-contrib/rules_nodejs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathterser_minified.bzl
204 lines (167 loc) · 7.02 KB
/
terser_minified.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# Copyright 2019 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.
"Rule to run the terser binary under bazel"
load("@build_bazel_rules_nodejs//:providers.bzl", "JSModuleInfo", "run_node")
_DOC = """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.
So the example above will output `out.min.js` and `out.min.js.map` (since `sourcemap` defaults to `true`).
If the input is a directory, then the output will also be a directory, named after the `name` attribute.
Note that this rule is **NOT** recursive. It assumes a flat file structure. Passing in a folder with nested folder
will result in an empty output directory.
"""
_TERSER_ATTRS = {
"args": attr.string_list(
doc = """Additional command line arguments to pass to terser.
Terser only parses minify() args from the config file so additional arguments such as `--comments` may
be passed to the rule using this attribute. See https://github.com/terser/terser#command-line-usage for the
full list of terser CLI options.""",
),
"config_file": attr.label(
doc = """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.
Run bazel with `--subcommands` to see the path to the copied file.
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 `--compilation_mode=dbg` bazel flag.
For example
```
{
"compress": {
"arrows": "bazel_no_debug"
}
}
```
Will disable the `arrows` compression setting when debugging.
If `config_file` isn't supplied, Bazel will use a default config file.
""",
allow_single_file = True,
# These defaults match how terser was run in the legacy built-in rollup_bundle rule.
# We keep them the same so it's easier for users to migrate.
default = Label("//packages/terser:terser_config.default.json"),
),
"debug": attr.bool(
doc = """Configure terser to produce more readable output.
Instead of setting this attribute, consider using debugging compilation mode instead
bazel build --compilation_mode=dbg //my/terser:target
so that it only affects the current build.
""",
),
"sourcemap": attr.bool(
doc = "Whether to produce a .js.map output",
default = True,
),
"src": attr.label(
doc = """File(s) to minify.
Can be a .js file, a rule producing .js files as its default output, or a rule producing a directory of .js files.
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.""",
allow_files = [".js", ".map", ".mjs"],
mandatory = True,
),
"terser_bin": attr.label(
doc = "An executable target that runs Terser",
default = Label("//packages/terser/bin:terser"),
executable = True,
cfg = "host",
),
}
def _filter_js(files):
return [f for f in files if f.is_directory or f.extension == "js" or f.extension == "mjs"]
def _terser(ctx):
"Generate actions to create terser config run terser"
# CLI arguments; see https://www.npmjs.com/package/terser#command-line-usage
args = ctx.actions.args()
inputs = []
outputs = []
# If src has a JSModuleInfo provider than use that otherwise use DefaultInfo files
if JSModuleInfo in ctx.attr.src:
inputs.extend(ctx.attr.src[JSModuleInfo].sources.to_list())
else:
inputs.extend(ctx.files.src[:])
sources = _filter_js(inputs)
sourcemaps = [f for f in inputs if f.extension == "map"]
directory_srcs = [s for s in sources if s.is_directory]
if len(directory_srcs) > 0:
if len(sources) > 1:
fail("When directories are passed to terser_minified, there should be only one input")
outputs.append(ctx.actions.declare_directory(ctx.label.name))
else:
outputs.append(ctx.actions.declare_file("%s.js" % ctx.label.name))
if ctx.attr.sourcemap:
outputs.append(ctx.actions.declare_file("%s.js.map" % ctx.label.name))
args.add_all([s.path for s in sources])
args.add_all(["--output", outputs[0].path])
debug = ctx.attr.debug or ctx.var["COMPILATION_MODE"] == "dbg"
if debug:
args.add("--debug")
args.add("--beautify")
if ctx.attr.sourcemap:
# Source mapping options are comma-packed into one argv
# see https://github.com/terser-js/terser#command-line-usage
source_map_opts = ["includeSources", "base=" + ctx.bin_dir.path]
if len(sourcemaps) == 0:
source_map_opts.append("content=inline")
elif len(sourcemaps) == 1:
source_map_opts.append("content='%s'" % sourcemaps[0].path)
else:
fail("When sourcemap is True, there should only be one or none input sourcemaps")
# Add a comment at the end of the js output so DevTools knows where to find the sourcemap
source_map_opts.append("url='%s.js.map'" % ctx.label.name)
# This option doesn't work in the config file, only on the CLI
args.add_all(["--source-map", ",".join(source_map_opts)])
opts = ctx.actions.declare_file("_%s.minify_options.json" % ctx.label.name)
inputs.append(opts)
ctx.actions.expand_template(
template = ctx.file.config_file,
output = opts,
substitutions = {
"\"bazel_debug\"": str(debug).lower(),
"\"bazel_no_debug\"": str(not debug).lower(),
},
)
args.add_all(["--config-file", opts.path])
args.add_all(ctx.attr.args)
run_node(
ctx,
inputs = inputs,
outputs = outputs,
executable = "terser_bin",
arguments = [args],
env = {"COMPILATION_MODE": ctx.var["COMPILATION_MODE"]},
progress_message = "Minifying JavaScript %s [terser]" % (outputs[0].short_path),
)
outputs_depset = depset(outputs)
return [
DefaultInfo(files = outputs_depset),
JSModuleInfo(
direct_sources = outputs_depset,
sources = outputs_depset,
),
]
terser_minified = rule(
doc = _DOC,
implementation = _terser,
attrs = _TERSER_ATTRS,
)