UNPKG

52.9 kBMarkdownView Raw
1terser
2======
3
4![Terser](https://raw.githubusercontent.com/terser-js/terser/master/logo.png)
5
6A JavaScript parser and mangler/compressor toolkit for ES6+.
7
8*note*: You can support this project on patreon: <a target="_blank" rel="nofollow" href="https://www.patreon.com/terser_ecmacomp_maintainer"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="patron" width="100px" height="auto"></a>. Check out PATRONS.md for our first-tier patrons.
9
10Terser recommends you use RollupJS to bundle your modules, as that produces smaller code overall.
11
12*Beautification* has been undocumented and is *being removed* from terser, we recommend you use [prettier](https://npmjs.com/package/prettier).
13
14[![Build Status](https://travis-ci.org/terser-js/terser.svg?branch=master)](https://travis-ci.org/terser-js/terser) [![Coverage Status](https://coveralls.io/repos/github/terser-js/terser/badge.svg?branch=master)](https://coveralls.io/github/terser-js/terser?branch=master)
15
16Find the changelog in [CHANGELOG.md](https://github.com/terser-js/terser/blob/master/CHANGELOG.md)
17
18
19Why choose terser?
20------------------
21
22`uglify-es` is [no longer maintained](https://github.com/mishoo/UglifyJS2/issues/3156#issuecomment-392943058) and `uglify-js` does not support ES6+.
23
24**`terser`** is a fork of `uglify-es` that retains API and CLI compatibility
25with `uglify-es` and `uglify-js@3`.
26
27Install
28-------
29
30First make sure you have installed the latest version of [node.js](http://nodejs.org/)
31(You may need to restart your computer after this step).
32
33From NPM for use as a command line app:
34
35 npm install terser -g
36
37From NPM for programmatic use:
38
39 npm install terser
40
41# Command line usage
42
43 terser [input files] [options]
44
45Terser can take multiple input files. It's recommended that you pass the
46input files first, then pass the options. Terser will parse input files
47in sequence and apply any compression options. The files are parsed in the
48same global scope, that is, a reference from a file to some
49variable/function declared in another file will be matched properly.
50
51If no input file is specified, Terser will read from STDIN.
52
53If you wish to pass your options before the input files, separate the two with
54a double dash to prevent input files being used as option arguments:
55
56 terser --compress --mangle -- input.js
57
58### Command line options
59
60```
61 -h, --help Print usage information.
62 `--help options` for details on available options.
63 -V, --version Print version number.
64 -p, --parse <options> Specify parser options:
65 `acorn` Use Acorn for parsing.
66 `bare_returns` Allow return outside of functions.
67 Useful when minifying CommonJS
68 modules and Userscripts that may
69 be anonymous function wrapped (IIFE)
70 by the .user.js engine `caller`.
71 `expression` Parse a single expression, rather than
72 a program (for parsing JSON).
73 `spidermonkey` Assume input files are SpiderMonkey
74 AST format (as JSON).
75 -c, --compress [options] Enable compressor/specify compressor options:
76 `pure_funcs` List of functions that can be safely
77 removed when their return values are
78 not used.
79 -m, --mangle [options] Mangle names/specify mangler options:
80 `reserved` List of names that should not be mangled.
81 --mangle-props [options] Mangle properties/specify mangler options:
82 `builtins` Mangle property names that overlaps
83 with standard JavaScript globals.
84 `debug` Add debug prefix and suffix.
85 `domprops` Mangle property names that overlaps
86 with DOM properties.
87 `keep_quoted` Only mangle unquoted properties.
88 `regex` Only mangle matched property names.
89 `reserved` List of names that should not be mangled.
90 `preamble` Preamble to prepend to the output. You
91 can use this to insert a comment, for
92 example for licensing information.
93 This will not be parsed, but the source
94 map will adjust for its presence.
95 `quote_style` Quote style:
96 0 - auto
97 1 - single
98 2 - double
99 3 - original
100 `wrap_iife` Wrap IIFEs in parenthesis. Note: you may
101 want to disable `negate_iife` under
102 compressor options.
103 -o, --output <file> Output file path (default STDOUT). Specify `ast` or
104 `spidermonkey` to write Terser or SpiderMonkey AST
105 as JSON to STDOUT respectively.
106 --comments [filter] Preserve copyright comments in the output. By
107 default this works like Google Closure, keeping
108 JSDoc-style comments that contain "@license" or
109 "@preserve". You can optionally pass one of the
110 following arguments to this flag:
111 - "all" to keep all comments
112 - a valid JS RegExp like `/foo/` or `/^!/` to
113 keep only matching comments.
114 Note that currently not *all* comments can be
115 kept when compression is on, because of dead
116 code removal or cascading statements into
117 sequences.
118 --config-file <file> Read `minify()` options from JSON file.
119 -d, --define <expr>[=value] Global definitions.
120 --ecma <version> Specify ECMAScript release: 5, 6, 7 or 8.
121 -e, --enclose [arg[:value]] Embed output in a big function with configurable
122 arguments and values.
123 --ie8 Support non-standard Internet Explorer 8.
124 Equivalent to setting `ie8: true` in `minify()`
125 for `compress`, `mangle` and `output` options.
126 By default Terser will not try to be IE-proof.
127 --keep-classnames Do not mangle/drop class names.
128 --keep-fnames Do not mangle/drop function names. Useful for
129 code relying on Function.prototype.name.
130 --module Input is an ES6 module. If `compress` or `mangle` is
131 enabled then the `toplevel` option will be enabled.
132 --name-cache <file> File to hold mangled name mappings.
133 --safari10 Support non-standard Safari 10/11.
134 Equivalent to setting `safari10: true` in `minify()`
135 for `mangle` and `output` options.
136 By default `terser` will not work around
137 Safari 10/11 bugs.
138 --source-map [options] Enable source map/specify source map options:
139 `base` Path to compute relative paths from input files.
140 `content` Input source map, useful if you're compressing
141 JS that was generated from some other original
142 code. Specify "inline" if the source map is
143 included within the sources.
144 `filename` Name and/or location of the output source.
145 `includeSources` Pass this flag if you want to include
146 the content of source files in the
147 source map as sourcesContent property.
148 `root` Path to the original source to be included in
149 the source map.
150 `url` If specified, path to the source map to append in
151 `//# sourceMappingURL`.
152 --timings Display operations run time on STDERR.
153 --toplevel Compress and/or mangle variables in top level scope.
154 --verbose Print diagnostic messages.
155 --warn Print warning messages.
156 --wrap <name> Embed everything in a big function, making the
157 “exports” and “global” variables available. You
158 need to pass an argument to this option to
159 specify the name that your module will take
160 when included in, say, a browser.
161```
162
163Specify `--output` (`-o`) to declare the output file. Otherwise the output
164goes to STDOUT.
165
166## CLI source map options
167
168Terser can generate a source map file, which is highly useful for
169debugging your compressed JavaScript. To get a source map, pass
170`--source-map --output output.js` (source map will be written out to
171`output.js.map`).
172
173Additional options:
174
175- `--source-map "filename='<NAME>'"` to specify the name of the source map.
176
177- `--source-map "root='<URL>'"` to pass the URL where the original files can be found.
178
179- `--source-map "url='<URL>'"` to specify the URL where the source map can be found.
180 Otherwise Terser assumes HTTP `X-SourceMap` is being used and will omit the
181 `//# sourceMappingURL=` directive.
182
183For example:
184
185 terser js/file1.js js/file2.js \
186 -o foo.min.js -c -m \
187 --source-map "root='http://foo.com/src',url='foo.min.js.map'"
188
189The above will compress and mangle `file1.js` and `file2.js`, will drop the
190output in `foo.min.js` and the source map in `foo.min.js.map`. The source
191mapping will refer to `http://foo.com/src/js/file1.js` and
192`http://foo.com/src/js/file2.js` (in fact it will list `http://foo.com/src`
193as the source map root, and the original files as `js/file1.js` and
194`js/file2.js`).
195
196### Composed source map
197
198When you're compressing JS code that was output by a compiler such as
199CoffeeScript, mapping to the JS code won't be too helpful. Instead, you'd
200like to map back to the original code (i.e. CoffeeScript). Terser has an
201option to take an input source map. Assuming you have a mapping from
202CoffeeScript → compiled JS, Terser can generate a map from CoffeeScript →
203compressed JS by mapping every token in the compiled JS to its original
204location.
205
206To use this feature pass `--source-map "content='/path/to/input/source.map'"`
207or `--source-map "content=inline"` if the source map is included inline with
208the sources.
209
210## CLI compress options
211
212You need to pass `--compress` (`-c`) to enable the compressor. Optionally
213you can pass a comma-separated list of [compress options](#compress-options).
214
215Options are in the form `foo=bar`, or just `foo` (the latter implies
216a boolean option that you want to set `true`; it's effectively a
217shortcut for `foo=true`).
218
219Example:
220
221 terser file.js -c toplevel,sequences=false
222
223## CLI mangle options
224
225To enable the mangler you need to pass `--mangle` (`-m`). The following
226(comma-separated) options are supported:
227
228- `toplevel` (default `false`) -- mangle names declared in the top level scope.
229
230- `eval` (default `false`) -- mangle names visible in scopes where `eval` or `with` are used.
231
232When mangling is enabled but you want to prevent certain names from being
233mangled, you can declare those names with `--mangle reserved` — pass a
234comma-separated list of names. For example:
235
236 terser ... -m reserved=['$','require','exports']
237
238to prevent the `require`, `exports` and `$` names from being changed.
239
240### CLI mangling property names (`--mangle-props`)
241
242**Note:** THIS *MIGHT* BREAK YOUR CODE. Mangling property names
243is a separate step, different from variable name mangling. Pass
244`--mangle-props` to enable it. It will mangle all properties in the
245input code with the exception of built in DOM properties and properties
246in core JavaScript classes. For example:
247
248```javascript
249// example.js
250var x = {
251 baz_: 0,
252 foo_: 1,
253 calc: function() {
254 return this.foo_ + this.baz_;
255 }
256};
257x.bar_ = 2;
258x["baz_"] = 3;
259console.log(x.calc());
260```
261Mangle all properties (except for JavaScript `builtins`):
262```bash
263$ terser example.js -c -m --mangle-props
264```
265```javascript
266var x={o:0,_:1,l:function(){return this._+this.o}};x.t=2,x.o=3,console.log(x.l());
267```
268Mangle all properties except for `reserved` properties:
269```bash
270$ terser example.js -c -m --mangle-props reserved=[foo_,bar_]
271```
272```javascript
273var x={o:0,foo_:1,_:function(){return this.foo_+this.o}};x.bar_=2,x.o=3,console.log(x._());
274```
275Mangle all properties matching a `regex`:
276```bash
277$ terser example.js -c -m --mangle-props regex=/_$/
278```
279```javascript
280var x={o:0,_:1,calc:function(){return this._+this.o}};x.l=2,x.o=3,console.log(x.calc());
281```
282
283Combining mangle properties options:
284```bash
285$ terser example.js -c -m --mangle-props regex=/_$/,reserved=[bar_]
286```
287```javascript
288var x={o:0,_:1,calc:function(){return this._+this.o}};x.bar_=2,x.o=3,console.log(x.calc());
289```
290
291In order for this to be of any use, we avoid mangling standard JS names by
292default (`--mangle-props builtins` to override).
293
294A default exclusion file is provided in `tools/domprops.json` which should
295cover most standard JS and DOM properties defined in various browsers. Pass
296`--mangle-props domprops` to disable this feature.
297
298A regular expression can be used to define which property names should be
299mangled. For example, `--mangle-props regex=/^_/` will only mangle property
300names that start with an underscore.
301
302When you compress multiple files using this option, in order for them to
303work together in the end we need to ensure somehow that one property gets
304mangled to the same name in all of them. For this, pass `--name-cache filename.json`
305and Terser will maintain these mappings in a file which can then be reused.
306It should be initially empty. Example:
307
308```bash
309$ rm -f /tmp/cache.json # start fresh
310$ terser file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
311$ terser file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js
312```
313
314Now, `part1.js` and `part2.js` will be consistent with each other in terms
315of mangled property names.
316
317Using the name cache is not necessary if you compress all your files in a
318single call to Terser.
319
320### Mangling unquoted names (`--mangle-props keep_quoted`)
321
322Using quoted property name (`o["foo"]`) reserves the property name (`foo`)
323so that it is not mangled throughout the entire script even when used in an
324unquoted style (`o.foo`). Example:
325
326```javascript
327// stuff.js
328var o = {
329 "foo": 1,
330 bar: 3
331};
332o.foo += o.bar;
333console.log(o.foo);
334```
335```bash
336$ terser stuff.js --mangle-props keep_quoted -c -m
337```
338```javascript
339var o={foo:1,o:3};o.foo+=o.o,console.log(o.foo);
340```
341
342### Debugging property name mangling
343
344You can also pass `--mangle-props debug` in order to mangle property names
345without completely obscuring them. For example the property `o.foo`
346would mangle to `o._$foo$_` with this option. This allows property mangling
347of a large codebase while still being able to debug the code and identify
348where mangling is breaking things.
349
350```bash
351$ terser stuff.js --mangle-props debug -c -m
352```
353```javascript
354var o={_$foo$_:1,_$bar$_:3};o._$foo$_+=o._$bar$_,console.log(o._$foo$_);
355```
356
357You can also pass a custom suffix using `--mangle-props debug=XYZ`. This would then
358mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a
359script to identify how a property got mangled. One technique is to pass a
360random number on every compile to simulate mangling changing with different
361inputs (e.g. as you update the input script with new properties), and to help
362identify mistakes like writing mangled keys to storage.
363
364
365# API Reference
366
367Assuming installation via NPM, you can load Terser in your application
368like this:
369```javascript
370var Terser = require("terser");
371```
372Browser loading is also supported:
373```html
374<script src="node_modules/source-map/dist/source-map.min.js"></script>
375<script src="dist/bundle.min.js"></script>
376```
377
378There is a single high level function, **`minify(code, options)`**,
379which will perform all minification [phases](#minify-options) in a configurable
380manner. By default `minify()` will enable the options [`compress`](#compress-options)
381and [`mangle`](#mangle-options). Example:
382```javascript
383var code = "function add(first, second) { return first + second; }";
384var result = Terser.minify(code);
385console.log(result.error); // runtime error, or `undefined` if no error
386console.log(result.code); // minified output: function add(n,d){return n+d}
387```
388
389You can `minify` more than one JavaScript file at a time by using an object
390for the first argument where the keys are file names and the values are source
391code:
392```javascript
393var code = {
394 "file1.js": "function add(first, second) { return first + second; }",
395 "file2.js": "console.log(add(1 + 2, 3 + 4));"
396};
397var result = Terser.minify(code);
398console.log(result.code);
399// function add(d,n){return d+n}console.log(add(3,7));
400```
401
402The `toplevel` option:
403```javascript
404var code = {
405 "file1.js": "function add(first, second) { return first + second; }",
406 "file2.js": "console.log(add(1 + 2, 3 + 4));"
407};
408var options = { toplevel: true };
409var result = Terser.minify(code, options);
410console.log(result.code);
411// console.log(3+7);
412```
413
414The `nameCache` option:
415```javascript
416var options = {
417 mangle: {
418 toplevel: true,
419 },
420 nameCache: {}
421};
422var result1 = Terser.minify({
423 "file1.js": "function add(first, second) { return first + second; }"
424}, options);
425var result2 = Terser.minify({
426 "file2.js": "console.log(add(1 + 2, 3 + 4));"
427}, options);
428console.log(result1.code);
429// function n(n,r){return n+r}
430console.log(result2.code);
431// console.log(n(3,7));
432```
433
434You may persist the name cache to the file system in the following way:
435```javascript
436var cacheFileName = "/tmp/cache.json";
437var options = {
438 mangle: {
439 properties: true,
440 },
441 nameCache: JSON.parse(fs.readFileSync(cacheFileName, "utf8"))
442};
443fs.writeFileSync("part1.js", Terser.minify({
444 "file1.js": fs.readFileSync("file1.js", "utf8"),
445 "file2.js": fs.readFileSync("file2.js", "utf8")
446}, options).code, "utf8");
447fs.writeFileSync("part2.js", Terser.minify({
448 "file3.js": fs.readFileSync("file3.js", "utf8"),
449 "file4.js": fs.readFileSync("file4.js", "utf8")
450}, options).code, "utf8");
451fs.writeFileSync(cacheFileName, JSON.stringify(options.nameCache), "utf8");
452```
453
454An example of a combination of `minify()` options:
455```javascript
456var code = {
457 "file1.js": "function add(first, second) { return first + second; }",
458 "file2.js": "console.log(add(1 + 2, 3 + 4));"
459};
460var options = {
461 toplevel: true,
462 compress: {
463 global_defs: {
464 "@console.log": "alert"
465 },
466 passes: 2
467 },
468 output: {
469 preamble: "/* minified */"
470 }
471};
472var result = Terser.minify(code, options);
473console.log(result.code);
474// /* minified */
475// alert(10);"
476```
477
478To produce warnings:
479```javascript
480var code = "function f(){ var u; return 2 + 3; }";
481var options = { warnings: true };
482var result = Terser.minify(code, options);
483console.log(result.error); // runtime error, `undefined` in this case
484console.log(result.warnings); // [ 'Dropping unused variable u [0:1,18]' ]
485console.log(result.code); // function f(){return 5}
486```
487
488An error example:
489```javascript
490var result = Terser.minify({"foo.js" : "if (0) else console.log(1);"});
491console.log(JSON.stringify(result.error));
492// {"message":"Unexpected token: keyword (else)","filename":"foo.js","line":1,"col":7,"pos":7}
493```
494Note: unlike `uglify-js@2.x`, the Terser API does not throw errors.
495To achieve a similar effect one could do the following:
496```javascript
497var result = Terser.minify(code, options);
498if (result.error) throw result.error;
499```
500
501## Minify options
502
503- `ecma` (default `undefined`) - pass `5`, `6`, `7` or `8` to override `parse`,
504 `compress` and `output` options.
505
506- `warnings` (default `false`) — pass `true` to return compressor warnings
507 in `result.warnings`. Use the value `"verbose"` for more detailed warnings.
508
509- `parse` (default `{}`) — pass an object if you wish to specify some
510 additional [parse options](#parse-options).
511
512- `compress` (default `{}`) — pass `false` to skip compressing entirely.
513 Pass an object to specify custom [compress options](#compress-options).
514
515- `mangle` (default `true`) — pass `false` to skip mangling names, or pass
516 an object to specify [mangle options](#mangle-options) (see below).
517
518 - `mangle.properties` (default `false`) — a subcategory of the mangle option.
519 Pass an object to specify custom [mangle property options](#mangle-properties-options).
520
521- `module` (default `false`) — Use when minifying an ES6 module. "use strict"
522 is implied and names can be mangled on the top scope. If `compress` or
523 `mangle` is enabled then the `toplevel` option will be enabled.
524
525- `output` (default `null`) — pass an object if you wish to specify
526 additional [output options](#output-options). The defaults are optimized
527 for best compression.
528
529- `sourceMap` (default `false`) - pass an object if you wish to specify
530 [source map options](#source-map-options).
531
532- `toplevel` (default `false`) - set to `true` if you wish to enable top level
533 variable and function name mangling and to drop unused variables and functions.
534
535- `nameCache` (default `null`) - pass an empty object `{}` or a previously
536 used `nameCache` object if you wish to cache mangled variable and
537 property names across multiple invocations of `minify()`. Note: this is
538 a read/write property. `minify()` will read the name cache state of this
539 object and update it during minification so that it may be
540 reused or externally persisted by the user.
541
542- `ie8` (default `false`) - set to `true` to support IE8.
543
544- `keep_classnames` (default: `undefined`) - pass `true` to prevent discarding or mangling
545 of class names. Pass a regular expression to only keep class names matching that regex.
546
547- `keep_fnames` (default: `false`) - pass `true` to prevent discarding or mangling
548 of function names. Pass a regular expression to only keep class names matching that regex.
549 Useful for code relying on `Function.prototype.name`. If the top level minify option
550 `keep_classnames` is `undefined` it will be overridden with the value of the top level
551 minify option `keep_fnames`.
552
553- `safari10` (default: `false`) - pass `true` to work around Safari 10/11 bugs in
554 loop scoping and `await`. See `safari10` options in [`mangle`](#mangle-options)
555 and [`output`](#output-options) for details.
556
557## Minify options structure
558
559```javascript
560{
561 parse: {
562 // parse options
563 },
564 compress: {
565 // compress options
566 },
567 mangle: {
568 // mangle options
569
570 properties: {
571 // mangle property options
572 }
573 },
574 output: {
575 // output options
576 },
577 sourceMap: {
578 // source map options
579 },
580 ecma: 5, // specify one of: 5, 6, 7 or 8
581 keep_classnames: false,
582 keep_fnames: false,
583 ie8: false,
584 module: false,
585 nameCache: null, // or specify a name cache object
586 safari10: false,
587 toplevel: false,
588 warnings: false,
589}
590```
591
592### Source map options
593
594To generate a source map:
595```javascript
596var result = Terser.minify({"file1.js": "var a = function() {};"}, {
597 sourceMap: {
598 filename: "out.js",
599 url: "out.js.map"
600 }
601});
602console.log(result.code); // minified output
603console.log(result.map); // source map
604```
605
606Note that the source map is not saved in a file, it's just returned in
607`result.map`. The value passed for `sourceMap.url` is only used to set
608`//# sourceMappingURL=out.js.map` in `result.code`. The value of
609`filename` is only used to set `file` attribute (see [the spec][sm-spec])
610in source map file.
611
612You can set option `sourceMap.url` to be `"inline"` and source map will
613be appended to code.
614
615You can also specify sourceRoot property to be included in source map:
616```javascript
617var result = Terser.minify({"file1.js": "var a = function() {};"}, {
618 sourceMap: {
619 root: "http://example.com/src",
620 url: "out.js.map"
621 }
622});
623```
624
625If you're compressing compiled JavaScript and have a source map for it, you
626can use `sourceMap.content`:
627```javascript
628var result = Terser.minify({"compiled.js": "compiled code"}, {
629 sourceMap: {
630 content: "content from compiled.js.map",
631 url: "minified.js.map"
632 }
633});
634// same as before, it returns `code` and `map`
635```
636
637If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`.
638
639## Parse options
640
641- `bare_returns` (default `false`) -- support top level `return` statements
642
643- `ecma` (default: `8`) -- specify one of `5`, `6`, `7` or `8`. Note: this setting
644 is not presently enforced except for ES8 optional trailing commas in function
645 parameter lists and calls with `ecma` `8`.
646
647- `html5_comments` (default `true`)
648
649- `shebang` (default `true`) -- support `#!command` as the first line
650
651## Compress options
652
653- `arrows` (default: `true`) -- Converts `()=>{return x}` to `()=>x`. Class
654 and object literal methods will also be converted to arrow expressions if
655 the resultant code is shorter: `m(){return x}` becomes `m:()=>x`.
656
657- `arguments` (default: `false`) -- replace `arguments[index]` with function
658 parameter name whenever possible.
659
660- `booleans` (default: `true`) -- various optimizations for boolean context,
661 for example `!!a ? b : c → a ? b : c`
662
663- `booleans_as_integers` (default: `false`) -- Turn booleans into 0 and 1, also
664 makes comparisons with booleans use `==` and `!=` instead of `===` and `!==`.
665
666- `collapse_vars` (default: `true`) -- Collapse single-use non-constant variables,
667 side effects permitting.
668
669- `comparisons` (default: `true`) -- apply certain optimizations to binary nodes,
670 e.g. `!(a <= b) → a > b` (only when `unsafe_comps`), attempts to negate binary
671 nodes, e.g. `a = !b && !c && !d && !e → a=!(b||c||d||e)` etc.
672
673- `computed_props` (default: `true`) -- Transforms constant computed properties
674 into regular ones: `{["computed"]: 1}` is converted to `{computed: 1}`.
675
676- `conditionals` (default: `true`) -- apply optimizations for `if`-s and conditional
677 expressions
678
679- `dead_code` (default: `true`) -- remove unreachable code
680
681- `defaults` (default: `true`) -- Pass `false` to disable most default
682 enabled `compress` transforms. Useful when you only want to enable a few
683 `compress` options while disabling the rest.
684
685- `directives` (default: `true`) -- remove redundant or non-standard directives
686
687- `drop_console` (default: `false`) -- Pass `true` to discard calls to
688 `console.*` functions. If you wish to drop a specific function call
689 such as `console.info` and/or retain side effects from function arguments
690 after dropping the function call then use `pure_funcs` instead.
691
692- `drop_debugger` (default: `true`) -- remove `debugger;` statements
693
694- `ecma` (default: `5`) -- Pass `6` or greater to enable `compress` options that
695 will transform ES5 code into smaller ES6+ equivalent forms.
696
697- `evaluate` (default: `true`) -- attempt to evaluate constant expressions
698
699- `expression` (default: `false`) -- Pass `true` to preserve completion values
700 from terminal statements without `return`, e.g. in bookmarklets.
701
702- `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation)
703
704- `hoist_funs` (default: `false`) -- hoist function declarations
705
706- `hoist_props` (default: `true`) -- hoist properties from constant object and
707 array literals into regular variables subject to a set of constraints. For example:
708 `var o={p:1, q:2}; f(o.p, o.q);` is converted to `f(1, 2);`. Note: `hoist_props`
709 works best with `mangle` enabled, the `compress` option `passes` set to `2` or higher,
710 and the `compress` option `toplevel` enabled.
711
712- `hoist_vars` (default: `false`) -- hoist `var` declarations (this is `false`
713 by default because it seems to increase the size of the output in general)
714
715- `if_return` (default: `true`) -- optimizations for if/return and if/continue
716
717- `inline` (default: `true`) -- inline calls to function with simple/`return` statement:
718 - `false` -- same as `0`
719 - `0` -- disabled inlining
720 - `1` -- inline simple functions
721 - `2` -- inline functions with arguments
722 - `3` -- inline functions with arguments and variables
723 - `true` -- same as `3`
724
725- `join_vars` (default: `true`) -- join consecutive `var` statements
726
727- `keep_classnames` (default: `false`) -- Pass `true` to prevent the compressor from
728 discarding class names. Pass a regular expression to only keep class names matching
729 that regex. See also: the `keep_classnames` [mangle option](#mangle).
730
731- `keep_fargs` (default: `true`) -- Prevents the compressor from discarding unused
732 function arguments. You need this for code which relies on `Function.length`.
733
734- `keep_fnames` (default: `false`) -- Pass `true` to prevent the
735 compressor from discarding function names. Pass a regular expression to only keep
736 class names matching that regex. Useful for code relying on `Function.prototype.name`.
737 See also: the `keep_fnames` [mangle option](#mangle).
738
739- `keep_infinity` (default: `false`) -- Pass `true` to prevent `Infinity` from
740 being compressed into `1/0`, which may cause performance issues on Chrome.
741
742- `loops` (default: `true`) -- optimizations for `do`, `while` and `for` loops
743 when we can statically determine the condition.
744
745- `module` (default `false`) -- Pass `true` when compressing an ES6 module. Strict
746 mode is implied and the `toplevel` option as well.
747
748- `negate_iife` (default: `true`) -- negate "Immediately-Called Function Expressions"
749 where the return value is discarded, to avoid the parens that the
750 code generator would insert.
751
752- `passes` (default: `1`) -- The maximum number of times to run compress.
753 In some cases more than one pass leads to further compressed code. Keep in
754 mind more passes will take more time.
755
756- `properties` (default: `true`) -- rewrite property access using the dot notation, for
757 example `foo["bar"] → foo.bar`
758
759- `pure_funcs` (default: `null`) -- You can pass an array of names and
760 Terser will assume that those functions do not produce side
761 effects. DANGER: will not check if the name is redefined in scope.
762 An example case here, for instance `var q = Math.floor(a/b)`. If
763 variable `q` is not used elsewhere, Terser will drop it, but will
764 still keep the `Math.floor(a/b)`, not knowing what it does. You can
765 pass `pure_funcs: [ 'Math.floor' ]` to let it know that this
766 function won't produce any side effect, in which case the whole
767 statement would get discarded. The current implementation adds some
768 overhead (compression will be slower).
769
770- `pure_getters` (default: `"strict"`) -- If you pass `true` for
771 this, Terser will assume that object property access
772 (e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects.
773 Specify `"strict"` to treat `foo.bar` as side-effect-free only when
774 `foo` is certain to not throw, i.e. not `null` or `undefined`.
775
776- `reduce_funcs` (default: `true`) -- Allows single-use functions to be
777 inlined as function expressions when permissible allowing further
778 optimization. Enabled by default. Option depends on `reduce_vars`
779 being enabled. Some code runs faster in the Chrome V8 engine if this
780 option is disabled. Does not negatively impact other major browsers.
781
782- `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and
783 used as constant values.
784
785- `sequences` (default: `true`) -- join consecutive simple statements using the
786 comma operator. May be set to a positive integer to specify the maximum number
787 of consecutive comma sequences that will be generated. If this option is set to
788 `true` then the default `sequences` limit is `200`. Set option to `false` or `0`
789 to disable. The smallest `sequences` length is `2`. A `sequences` value of `1`
790 is grandfathered to be equivalent to `true` and as such means `200`. On rare
791 occasions the default sequences limit leads to very slow compress times in which
792 case a value of `20` or less is recommended.
793
794- `side_effects` (default: `true`) -- Pass `false` to disable potentially dropping
795 functions marked as "pure". A function call is marked as "pure" if a comment
796 annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
797 example: `/*@__PURE__*/foo();`
798
799- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches
800
801- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or
802 variables (`"vars"`) in the top level scope (`false` by default, `true` to drop
803 both unreferenced functions and variables)
804
805- `top_retain` (default: `null`) -- prevent specific toplevel functions and
806 variables from `unused` removal (can be array, comma-separated, RegExp or
807 function. Implies `toplevel`)
808
809- `typeofs` (default: `true`) -- Transforms `typeof foo == "undefined"` into
810 `foo === void 0`. Note: recommend to set this value to `false` for IE10 and
811 earlier versions due to known issues.
812
813- `unsafe` (default: `false`) -- apply "unsafe" transformations
814 ([details](#the-unsafe-compress-option)).
815
816- `unsafe_arrows` (default: `false`) -- Convert ES5 style anonymous function
817 expressions to arrow functions if the function body does not reference `this`.
818 Note: it is not always safe to perform this conversion if code relies on the
819 the function having a `prototype`, which arrow functions lack.
820 This transform requires that the `ecma` compress option is set to `6` or greater.
821
822- `unsafe_comps` (default: `false`) -- Reverse `<` and `<=` to `>` and `>=` to
823 allow improved compression. This might be unsafe when an at least one of two
824 operands is an object with computed values due the use of methods like `get`,
825 or `valueOf`. This could cause change in execution order after operands in the
826 comparison are switching. Compression only works if both `comparisons` and
827 `unsafe_comps` are both set to true.
828
829- `unsafe_Function` (default: `false`) -- compress and mangle `Function(args, code)`
830 when both `args` and `code` are string literals.
831
832- `unsafe_math` (default: `false`) -- optimize numerical expressions like
833 `2 * x * 3` into `6 * x`, which may give imprecise floating point results.
834
835- `unsafe_methods` (default: false) -- Converts `{ m: function(){} }` to
836 `{ m(){} }`. `ecma` must be set to `6` or greater to enable this transform.
837 If `unsafe_methods` is a RegExp then key/value pairs with keys matching the
838 RegExp will be converted to concise methods.
839 Note: if enabled there is a risk of getting a "`<method name>` is not a
840 constructor" TypeError should any code try to `new` the former function.
841
842- `unsafe_proto` (default: `false`) -- optimize expressions like
843 `Array.prototype.slice.call(a)` into `[].slice.call(a)`
844
845- `unsafe_regexp` (default: `false`) -- enable substitutions of variables with
846 `RegExp` values the same way as if they are constants.
847
848- `unsafe_undefined` (default: `false`) -- substitute `void 0` if there is a
849 variable named `undefined` in scope (variable name will be mangled, typically
850 reduced to a single character)
851
852- `unused` (default: `true`) -- drop unreferenced functions and variables (simple
853 direct variable assignments do not count as references unless set to `"keep_assign"`)
854
855- `warnings` (default: `false`) -- display warnings when dropping unreachable
856 code or unused declarations etc.
857
858## Mangle options
859
860- `eval` (default `false`) -- Pass `true` to mangle names visible in scopes
861 where `eval` or `with` are used.
862
863- `keep_classnames` (default `false`) -- Pass `true` to not mangle class names.
864 Pass a regular expression to only keep class names matching that regex.
865 See also: the `keep_classnames` [compress option](#compress-options).
866
867- `keep_fnames` (default `false`) -- Pass `true` to not mangle function names.
868 Pass a regular expression to only keep class names matching that regex.
869 Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames`
870 [compress option](#compress-options).
871
872- `module` (default `false`) -- Pass `true` an ES6 modules, where the toplevel
873 scope is not the global scope. Implies `toplevel`.
874
875- `reserved` (default `[]`) -- Pass an array of identifiers that should be
876 excluded from mangling. Example: `["foo", "bar"]`.
877
878- `toplevel` (default `false`) -- Pass `true` to mangle names declared in the
879 top level scope.
880
881- `safari10` (default `false`) -- Pass `true` to work around the Safari 10 loop
882 iterator [bug](https://bugs.webkit.org/show_bug.cgi?id=171041)
883 "Cannot declare a let variable twice".
884 See also: the `safari10` [output option](#output-options).
885
886Examples:
887
888```javascript
889// test.js
890var globalVar;
891function funcName(firstLongName, anotherLongName) {
892 var myVariable = firstLongName + anotherLongName;
893}
894```
895```javascript
896var code = fs.readFileSync("test.js", "utf8");
897
898Terser.minify(code).code;
899// 'function funcName(a,n){}var globalVar;'
900
901Terser.minify(code, { mangle: { reserved: ['firstLongName'] } }).code;
902// 'function funcName(firstLongName,a){}var globalVar;'
903
904Terser.minify(code, { mangle: { toplevel: true } }).code;
905// 'function n(n,a){}var a;'
906```
907
908### Mangle properties options
909
910- `builtins` (default: `false`) -- Use `true` to allow the mangling of builtin
911 DOM properties. Not recommended to override this setting.
912
913- `debug` (default: `false`) -— Mangle names with the original name still present.
914 Pass an empty string `""` to enable, or a non-empty string to set the debug suffix.
915
916- `keep_quoted` (default: `false`) -— Only mangle unquoted property names.
917
918- `regex` (default: `null`) -— Pass a RegExp literal to only mangle property
919 names matching the regular expression.
920
921- `reserved` (default: `[]`) -- Do not mangle property names listed in the
922 `reserved` array.
923
924## Output options
925
926The code generator tries to output shortest code possible. Optionally you
927can pass additional arguments that control the code output:
928
929- `ascii_only` (default `false`) -- escape Unicode characters in strings and
930 regexps (affects directives with non-ascii characters becoming invalid)
931
932- `braces` (default `false`) -- always insert braces in `if`, `for`,
933 `do`, `while` or `with` statements, even if their body is a single
934 statement.
935
936- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
937 comments, `"some"` to preserve some comments, a regular expression string
938 (e.g. `/^!/`) or a function.
939
940- `ecma` (default `5`) -- set output printing mode. Set `ecma` to `6` or
941 greater to emit shorthand object properties - i.e.: `{a}` instead of `{a: a}`.
942 Non-compatible features in the abstract syntax tree will still
943 be output as is. For example: an `ecma` setting of `5` will **not** convert
944 ES6+ code to ES5.
945
946- `indent_level` (default `4`)
947
948- `indent_start` (default `0`) -- prefix all lines by that many spaces
949
950- `inline_script` (default `true`) -- escape HTML comments and the slash in
951 occurrences of `</script>` in strings
952
953- `keep_quoted_props` (default `false`) -- when turned on, prevents stripping
954 quotes from property names in object literals.
955
956- `max_line_len` (default `false`) -- maximum line length (for minified code)
957
958- `preamble` (default `null`) -- when passed it must be a string and
959 it will be prepended to the output literally. The source map will
960 adjust for this text. Can be used to insert a comment containing
961 licensing information, for example.
962
963- `quote_keys` (default `false`) -- pass `true` to quote all keys in literal
964 objects
965
966- `quote_style` (default `0`) -- preferred quote style for strings (affects
967 quoted property names and directives as well):
968 - `0` -- prefers double quotes, switches to single quotes when there are
969 more double quotes in the string itself. `0` is best for gzip size.
970 - `1` -- always use single quotes
971 - `2` -- always use double quotes
972 - `3` -- always use the original quotes
973
974- `safari10` (default `false`) -- set this option to `true` to work around
975 the [Safari 10/11 await bug](https://bugs.webkit.org/show_bug.cgi?id=176685).
976 See also: the `safari10` [mangle option](#mangle-options).
977
978- `semicolons` (default `true`) -- separate statements with semicolons. If
979 you pass `false` then whenever possible we will use a newline instead of a
980 semicolon, leading to more readable output of minified code (size before
981 gzip could be smaller; size after gzip insignificantly larger).
982
983- `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
984
985- `webkit` (default `false`) -- enable workarounds for WebKit bugs.
986 PhantomJS users should set this option to `true`.
987
988- `wrap_iife` (default `false`) -- pass `true` to wrap immediately invoked
989 function expressions. See
990 [#640](https://github.com/mishoo/UglifyJS2/issues/640) for more details.
991
992# Miscellaneous
993
994### Keeping copyright notices or other comments
995
996You can pass `--comments` to retain certain comments in the output. By
997default it will keep JSDoc-style comments that contain "@preserve",
998"@license" or "@cc_on" (conditional compilation for IE). You can pass
999`--comments all` to keep all the comments, or a valid JavaScript regexp to
1000keep only comments that match this regexp. For example `--comments /^!/`
1001will keep comments like `/*! Copyright Notice */`.
1002
1003Note, however, that there might be situations where comments are lost. For
1004example:
1005```javascript
1006function f() {
1007 /** @preserve Foo Bar */
1008 function g() {
1009 // this function is never called
1010 }
1011 return something();
1012}
1013```
1014
1015Even though it has "@preserve", the comment will be lost because the inner
1016function `g` (which is the AST node to which the comment is attached to) is
1017discarded by the compressor as not referenced.
1018
1019The safest comments where to place copyright information (or other info that
1020needs to be kept in the output) are comments attached to toplevel nodes.
1021
1022### The `unsafe` `compress` option
1023
1024It enables some transformations that *might* break code logic in certain
1025contrived cases, but should be fine for most code. It assumes that standard
1026built-in ECMAScript functions and classes have not been altered or replaced.
1027You might want to try it on your own code; it should reduce the minified size.
1028Some examples of the optimizations made when this option is enabled:
1029
1030- `new Array(1, 2, 3)` or `Array(1, 2, 3)``[ 1, 2, 3 ]`
1031- `new Object()``{}`
1032- `String(exp)` or `exp.toString()``"" + exp`
1033- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
1034- `"foo bar".substr(4)``"bar"`
1035
1036### Conditional compilation
1037
1038You can use the `--define` (`-d`) switch in order to declare global
1039variables that Terser will assume to be constants (unless defined in
1040scope). For example if you pass `--define DEBUG=false` then, coupled with
1041dead code removal Terser will discard the following from the output:
1042```javascript
1043if (DEBUG) {
1044 console.log("debug stuff");
1045}
1046```
1047
1048You can specify nested constants in the form of `--define env.DEBUG=false`.
1049
1050Terser will warn about the condition being always false and about dropping
1051unreachable code; for now there is no option to turn off only this specific
1052warning, you can pass `warnings=false` to turn off *all* warnings.
1053
1054Another way of doing that is to declare your globals as constants in a
1055separate file and include it into the build. For example you can have a
1056`build/defines.js` file with the following:
1057```javascript
1058var DEBUG = false;
1059var PRODUCTION = true;
1060// etc.
1061```
1062
1063and build your code like this:
1064
1065 terser build/defines.js js/foo.js js/bar.js... -c
1066
1067Terser will notice the constants and, since they cannot be altered, it
1068will evaluate references to them to the value itself and drop unreachable
1069code as usual. The build will contain the `const` declarations if you use
1070them. If you are targeting < ES6 environments which does not support `const`,
1071using `var` with `reduce_vars` (enabled by default) should suffice.
1072
1073### Conditional compilation API
1074
1075You can also use conditional compilation via the programmatic API. With the difference that the
1076property name is `global_defs` and is a compressor property:
1077
1078```javascript
1079var result = Terser.minify(fs.readFileSync("input.js", "utf8"), {
1080 compress: {
1081 dead_code: true,
1082 global_defs: {
1083 DEBUG: false
1084 }
1085 }
1086});
1087```
1088
1089To replace an identifier with an arbitrary non-constant expression it is
1090necessary to prefix the `global_defs` key with `"@"` to instruct Terser
1091to parse the value as an expression:
1092```javascript
1093Terser.minify("alert('hello');", {
1094 compress: {
1095 global_defs: {
1096 "@alert": "console.log"
1097 }
1098 }
1099}).code;
1100// returns: 'console.log("hello");'
1101```
1102
1103Otherwise it would be replaced as string literal:
1104```javascript
1105Terser.minify("alert('hello');", {
1106 compress: {
1107 global_defs: {
1108 "alert": "console.log"
1109 }
1110 }
1111}).code;
1112// returns: '"console.log"("hello");'
1113```
1114
1115### Using native Terser AST with `minify()`
1116```javascript
1117// example: parse only, produce native Terser AST
1118
1119var result = Terser.minify(code, {
1120 parse: {},
1121 compress: false,
1122 mangle: false,
1123 output: {
1124 ast: true,
1125 code: false // optional - faster if false
1126 }
1127});
1128
1129// result.ast contains native Terser AST
1130```
1131```javascript
1132// example: accept native Terser AST input and then compress and mangle
1133// to produce both code and native AST.
1134
1135var result = Terser.minify(ast, {
1136 compress: {},
1137 mangle: {},
1138 output: {
1139 ast: true,
1140 code: true // optional - faster if false
1141 }
1142});
1143
1144// result.ast contains native Terser AST
1145// result.code contains the minified code in string form.
1146```
1147
1148### Working with Terser AST
1149
1150Traversal and transformation of the native AST can be performed through
1151[`TreeWalker`](https://github.com/fabiosantoscode/terser/blob/master/lib/ast.js) and
1152[`TreeTransformer`](https://github.com/fabiosantoscode/terser/blob/master/lib/transform.js)
1153respectively.
1154
1155Largely compatible native AST examples can be found in the original UglifyJS
1156documentation. See: [tree walker](http://lisperator.net/uglifyjs/walk) and
1157[tree transform](http://lisperator.net/uglifyjs/transform).
1158
1159### ESTree / SpiderMonkey AST
1160
1161Terser has its own abstract syntax tree format; for
1162[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
1163we can't easily change to using the SpiderMonkey AST internally. However,
1164Terser now has a converter which can import a SpiderMonkey AST.
1165
1166For example [Acorn][acorn] is a super-fast parser that produces a
1167SpiderMonkey AST. It has a small CLI utility that parses one file and dumps
1168the AST in JSON on the standard output. To use Terser to mangle and
1169compress that:
1170
1171 acorn file.js | terser -p spidermonkey -m -c
1172
1173The `-p spidermonkey` option tells Terser that all input files are not
1174JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we
1175don't use our own parser in this case, but just transform that AST into our
1176internal AST.
1177
1178### Use Acorn for parsing
1179
1180More for fun, I added the `-p acorn` option which will use Acorn to do all
1181the parsing. If you pass this option, Terser will `require("acorn")`.
1182
1183Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
1184converting the SpiderMonkey tree that Acorn produces takes another 150ms so
1185in total it's a bit more than just using Terser's own parser.
1186
1187[acorn]: https://github.com/ternjs/acorn
1188[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
1189
1190### Terser Fast Minify Mode
1191
1192It's not well known, but whitespace removal and symbol mangling accounts
1193for 95% of the size reduction in minified code for most JavaScript - not
1194elaborate code transforms. One can simply disable `compress` to speed up
1195Terser builds by 3 to 4 times.
1196
1197| d3.js | size | gzip size | time (s) |
1198| --- | ---: | ---: | ---: |
1199| original | 451,131 | 108,733 | - |
1200| terser@3.7.5 mangle=false, compress=false | 316,600 | 85,245 | 0.82 |
1201| terser@3.7.5 mangle=true, compress=false | 220,216 | 72,730 | 1.45 |
1202| terser@3.7.5 mangle=true, compress=true | 212,046 | 70,954 | 5.87 |
1203| babili@0.1.4 | 210,713 | 72,140 | 12.64 |
1204| babel-minify@0.4.3 | 210,321 | 72,242 | 48.67 |
1205| babel-minify@0.5.0-alpha.01eac1c3 | 210,421 | 72,238 | 14.17 |
1206
1207To enable fast minify mode from the CLI use:
1208```
1209terser file.js -m
1210```
1211To enable fast minify mode with the API use:
1212```js
1213Terser.minify(code, { compress: false, mangle: true });
1214```
1215
1216#### Source maps and debugging
1217
1218Various `compress` transforms that simplify, rearrange, inline and remove code
1219are known to have an adverse effect on debugging with source maps. This is
1220expected as code is optimized and mappings are often simply not possible as
1221some code no longer exists. For highest fidelity in source map debugging
1222disable the `compress` option and just use `mangle`.
1223
1224### Compiler assumptions
1225
1226To allow for better optimizations, the compiler makes various assumptions:
1227
1228- `.toString()` and `.valueOf()` don't have side effects, and for built-in
1229 objects they have not been overridden.
1230- `undefined`, `NaN` and `Infinity` have not been externally redefined.
1231- `arguments.callee`, `arguments.caller` and `Function.prototype.caller` are not used.
1232- The code doesn't expect the contents of `Function.prototype.toString()` or
1233 `Error.prototype.stack` to be anything in particular.
1234- Getting and setting properties on a plain object does not cause other side effects
1235 (using `.watch()` or `Proxy`).
1236- Object properties can be added, removed and modified (not prevented with
1237 `Object.defineProperty()`, `Object.defineProperties()`, `Object.freeze()`,
1238 `Object.preventExtensions()` or `Object.seal()`).
1239
1240### Build Tools and Adaptors using Terser
1241
1242https://www.npmjs.com/browse/depended/terser
1243
1244### Replacing `uglify-es` with `terser` in a project using `yarn`
1245
1246A number of JS bundlers and uglify wrappers are still using buggy versions
1247of `uglify-es` and have not yet upgraded to `terser`. If you are using `yarn`
1248you can add the following alias to your project's `package.json` file:
1249
1250```js
1251 "resolutions": {
1252 "uglify-es": "npm:terser"
1253 }
1254```
1255
1256to use `terser` instead of `uglify-es` in all deeply nested dependencies
1257without changing any code.
1258
1259Note: for this change to take effect you must run the following commands
1260to remove the existing `yarn` lock file and reinstall all packages:
1261
1262```
1263$ rm -rf node_modules yarn.lock
1264$ yarn
1265```
1266
1267# Reporting issues
1268
1269In the terser CLI we use [source-map-support](https://npmjs.com/source-map-support) to produce good error stacks. In your own app, you're expected to enable source-map-support (read their docs) to have nice stack traces that will make good issues.
1270
1271# README.md Patrons:
1272
1273*note*: You can support this project on patreon: <a target="_blank" rel="nofollow" href="https://www.patreon.com/terser_ecmacomp_maintainer"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="patron" width="100px" height="auto"></a>. Check out PATRONS.md for our first-tier patrons.
1274
1275
1276 * CKEditor ![](https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/15452278/f8548dcf48d740619071e8d614459280/1?token-time=2145916800&token-hash=SIQ54PhIPHv3M7CVz9LxS8_8v4sOw4H304HaXsXj8MM%3D)
1277 * 38elements ![](https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12501844/88e7fc5dd62d45c6a5626533bbd48cfb/1?token-time=2145916800&token-hash=c3AsQ5T0IQWic0zKxFHu-bGGQJkXQFvafvJ4bPerFR4%3D)