Go minifiers for web formats

Minify API reference Go Report Card Coverage Status Donate

Online demo if you need to minify files now.

Command line tool that minifies concurrently and watches file changes.

Releases of CLI for various platforms. See CLI for more installation instructions.


Did you know that the shortest valid piece of HTML5 is <!doctype html><title>x</title>? See for yourself at the W3C Validator!

Minify is a minifier package written in Go. It provides HTML5, CSS3, JS, JSON, SVG and XML minifiers and an interface to implement any other minifier. Minification is the process of removing bytes from a file (such as whitespace) without changing its output and therefore shrinking its size and speeding up transmission over the internet and possibly parsing. The implemented minifiers are designed for high performance.

The core functionality associates mimetypes with minification functions, allowing embedded resources (like CSS or JS within HTML files) to be minified as well. Users can add new implementations that are triggered based on a mimetype (or pattern), or redirect to an external command (like ClosureCompiler, UglifyCSS, ...).

Sponsors

SiteGround

Please see https://www.patreon.com/tdewolff for ways to contribute, otherwise please contact me directly!

Table of Contents

Roadmap

  • Use ASM/SSE to further speed-up core parts of the parsers/minifiers
  • Improve JS minifiers by shortening variables and proper semicolon omission
  • Speed-up SVG minifier, it is very slow
  • Proper parser error reporting and line number + column information
  • Generation of source maps (uncertain, might slow down parsers too much if it cannot run separately nicely)
  • Create a cmd to pack webfiles (much like webpack), ie. merging CSS and JS files, inlining small external files, minification and gzipping. This would work on HTML files.

Prologue

Minifiers or bindings to minifiers exist in almost all programming languages. Some implementations are merely using several regular expressions to trim whitespace and comments (even though regex for parsing HTML/XML is ill-advised, for a good read see Regular Expressions: Now You Have Two Problems). Some implementations are much more profound, such as the YUI Compressor and Google Closure Compiler for JS. As most existing implementations either use JavaScript, use regexes, and don't focus on performance, they are pretty slow.

This minifier proves to be that fast and extensive minifier that can handle HTML and any other filetype it may contain (CSS, JS, ...). It is usually orders of magnitude faster than existing minifiers.

Installation

With modules enabled (GO111MODULES=auto or GO111MODULES=on), add the following imports and run the project with go get

import (
	"github.com/tdewolff/minify/v2"
	"github.com/tdewolff/minify/v2/css"
	"github.com/tdewolff/minify/v2/html"
	"github.com/tdewolff/minify/v2/js"
	"github.com/tdewolff/minify/v2/json"
	"github.com/tdewolff/minify/v2/svg"
	"github.com/tdewolff/minify/v2/xml"
)

See CLI tool for installation instructions of the binary.

Docker

If you want to use Docker, please see https://hub.docker.com/r/tdewolff/minify.

API stability

There is no guarantee for absolute stability, but I take issues and bugs seriously and don't take API changes lightly. The library will be maintained in a compatible way unless vital bugs prevent me from doing so. There has been one API change after v1 which added options support and I took the opportunity to push through some more API clean up as well. There are no plans whatsoever for future API changes.

Testing

For all subpackages and the imported parse package, test coverage of 100% is pursued. Besides full coverage, the minifiers are fuzz tested using github.com/dvyukov/go-fuzz, see the wiki for the most important bugs found by fuzz testing. These tests ensure that everything works as intended and that the code does not crash (whatever the input). If you still encounter a bug, please file a bug report!

Performance

The benchmarks directory contains a number of standardized samples used to compare performance between changes. To give an indication of the speed of this library, I've ran the tests on my Thinkpad T460 (i5-6300U quad-core 2.4GHz running Arch Linux) using Go 1.15.

name                              time/op
CSS/sample_bootstrap.css-4          2.70ms ± 0%
CSS/sample_gumby.css-4              3.57ms ± 0%
CSS/sample_fontawesome.css-4         767µs ± 0%
CSS/sample_normalize.css-4          85.5µs ± 0%
HTML/sample_amazon.html-4           15.2ms ± 0%
HTML/sample_bbc.html-4              3.90ms ± 0%
HTML/sample_blogpost.html-4          420µs ± 0%
HTML/sample_es6.html-4              15.6ms ± 0%
HTML/sample_stackoverflow.html-4    3.73ms ± 0%
HTML/sample_wikipedia.html-4        6.60ms ± 0%
JS/sample_ace.js-4                  28.7ms ± 0%
JS/sample_dot.js-4                   357µs ± 0%
JS/sample_jquery.js-4               10.0ms ± 0%
JS/sample_jqueryui.js-4             20.4ms ± 0%
JS/sample_moment.js-4               3.47ms ± 0%
JSON/sample_large.json-4            3.25ms ± 0%
JSON/sample_testsuite.json-4        1.74ms ± 0%
JSON/sample_twitter.json-4          24.2µs ± 0%
SVG/sample_arctic.svg-4             34.7ms ± 0%
SVG/sample_gopher.svg-4              307µs ± 0%
SVG/sample_usa.svg-4                57.4ms ± 0%
SVG/sample_car.svg-4                18.0ms ± 0%
SVG/sample_tiger.svg-4              5.61ms ± 0%
XML/sample_books.xml-4              54.7µs ± 0%
XML/sample_catalog.xml-4            33.0µs ± 0%
XML/sample_omg.xml-4                7.17ms ± 0%

name                              speed
CSS/sample_bootstrap.css-4        50.7MB/s ± 0%
CSS/sample_gumby.css-4            52.1MB/s ± 0%
CSS/sample_fontawesome.css-4      61.2MB/s ± 0%
CSS/sample_normalize.css-4        70.8MB/s ± 0%
HTML/sample_amazon.html-4         31.1MB/s ± 0%
HTML/sample_bbc.html-4            29.5MB/s ± 0%
HTML/sample_blogpost.html-4       49.8MB/s ± 0%
HTML/sample_es6.html-4            65.6MB/s ± 0%
HTML/sample_stackoverflow.html-4  55.0MB/s ± 0%
HTML/sample_wikipedia.html-4      67.5MB/s ± 0%
JS/sample_ace.js-4                22.4MB/s ± 0%
JS/sample_dot.js-4                14.5MB/s ± 0%
JS/sample_jquery.js-4             24.8MB/s ± 0%
JS/sample_jqueryui.js-4           23.0MB/s ± 0%
JS/sample_moment.js-4             28.6MB/s ± 0%
JSON/sample_large.json-4           234MB/s ± 0%
JSON/sample_testsuite.json-4       394MB/s ± 0%
JSON/sample_twitter.json-4        63.0MB/s ± 0%
SVG/sample_arctic.svg-4           42.4MB/s ± 0%
SVG/sample_gopher.svg-4           19.0MB/s ± 0%
SVG/sample_usa.svg-4              17.8MB/s ± 0%
SVG/sample_car.svg-4              29.3MB/s ± 0%
SVG/sample_tiger.svg-4            12.2MB/s ± 0%
XML/sample_books.xml-4            81.0MB/s ± 0%
XML/sample_catalog.xml-4          58.6MB/s ± 0%
XML/sample_omg.xml-4               159MB/s ± 0%

HTML

HTML (with JS and CSS) minification typically shaves off about 10%.

The HTML5 minifier uses these minifications:

  • strip unnecessary whitespace and otherwise collapse it to one space (or newline if it originally contained a newline)
  • strip superfluous quotes, or uses single/double quotes whichever requires fewer escapes
  • strip default attribute values and attribute boolean values
  • strip some empty attributes
  • strip unrequired tags (html, head, body, ...)
  • strip unrequired end tags (tr, td, li, ... and often p)
  • strip default protocols (http:, https: and javascript:)
  • strip all comments (including conditional comments, old IE versions are not supported anymore by Microsoft)
  • shorten doctype and meta charset
  • lowercase tags, attributes and some values to enhance gzip compression

Options:

  • KeepConditionalComments preserve all IE conditional comments such as <!--[if IE 6]><![endif]--> and <![if IE 6]><![endif]>, see https://msdn.microsoft.com/en-us/library/ms537512(v=vs.85).aspx#syntax
  • KeepDefaultAttrVals preserve default attribute values such as <script type="application/javascript">
  • KeepDocumentTags preserve html, head and body tags
  • KeepEndTags preserve all end tags
  • KeepQuotes preserve quotes around attribute values
  • KeepWhitespace preserve whitespace between inline tags but still collapse multiple whitespace characters into one

After recent benchmarking and profiling it became really fast and minifies pages in the 10ms range, making it viable for on-the-fly minification.

However, be careful when doing on-the-fly minification. Minification typically trims off 10% and does this at worst around about 20MB/s. This means users have to download slower than 2MB/s to make on-the-fly minification worthwhile. This may or may not apply in your situation. Rather use caching!

Whitespace removal

The whitespace removal mechanism collapses all sequences of whitespace (spaces, newlines, tabs) to a single space. If the sequence contained a newline or carriage return it will collapse into a newline character instead. It trims all text parts (in between tags) depending on whether it was preceded by a space from a previous piece of text and whether it is followed up by a block element or an inline element. In the former case we can omit spaces while for inline elements whitespace has significance.

Make sure your HTML doesn't depend on whitespace between block elements that have been changed to inline or inline-block elements using CSS. Your layout should not depend on those whitespaces as the minifier will remove them. An example is a menu consisting of multiple <li> that have display:inline-block applied and have whitespace in between them. It is bad practise to rely on whitespace for element positioning anyways!

CSS

Minification typically shaves off about 10%-15%. This CSS minifier will not do structural changes to your stylesheets. Although this could result in smaller files, the complexity is quite high and the risk of breaking website is high too.

The CSS minifier will only use safe minifications:

  • remove comments and unnecessary whitespace (but keep /*! ... */ which usually contains the license)
  • remove trailing semicolons
  • optimize margin, padding and border-width number of sides
  • shorten numbers by removing unnecessary + and zeros and rewriting with/without exponent
  • remove dimension and percentage for zero values
  • remove quotes for URLs
  • remove quotes for font families and make lowercase
  • rewrite hex colors to/from color names, or to three digit hex
  • rewrite rgb(, rgba(, hsl( and hsla( colors to hex or name
  • use four digit hex for alpha values (transparent#0000)
  • replace normal and bold by numbers for font-weight and font
  • replace none0 for border, background and outline
  • lowercase all identifiers except classes, IDs and URLs to enhance gzip compression
  • shorten MS alpha function
  • rewrite data URIs with base64 or ASCII whichever is shorter
  • calls minifier for data URI mediatypes, thus you can compress embedded SVG files if you have that minifier attached
  • shorten aggregate declarations such as background and font

It does purposely not use the following techniques:

  • (partially) merge rulesets
  • (partially) split rulesets
  • collapse multiple declarations when main declaration is defined within a ruleset (don't put font-weight within an already existing font, too complex)
  • remove overwritten properties in ruleset (this not always overwrites it, for example with !important)
  • rewrite properties into one ruleset if possible (like margin-top, margin-right, margin-bottom and margin-leftmargin)
  • put nested ID selector at the front (body > div#elem p#elem p)
  • rewrite attribute selectors for IDs and classes (div[id=a]div#a)
  • put space after pseudo-selectors (IE6 is old, move on!)

There are a couple of comparison tables online, such as CSS Minifier Comparison, CSS minifiers comparison and CleanCSS tests. Comparing speed between each, this minifier will usually be between 10x-300x faster than existing implementations, and even rank among the top for minification ratios. It falls short with the purposely not implemented and often unsafe techniques.

Options:

  • KeepCSS2 prohibits using CSS3 syntax (such as exponents in numbers, or rgba(rgb(), might be incomplete
  • Precision number of significant digits to preserve for numbers, 0 means no trimming

JS

The JS minifier typically shaves off about 35% -- 65% of filesize depening on the file, which is a compression close to many other minifiers. Common speeds of PHP and JS implementations are about 100-300kB/s (see Uglify2, Adventures in PHP web asset minimization). This implementation is orders of magnitude faster at around ~25MB/s.

The following features are implemented:

  • remove superfluous whitespace
  • remove superfluous semicolons
  • shorten true, false, and undefined to !0, !1 and void 0
  • rename variables and functions to shorter names (not in global scope)
  • move var declarations to the top of the global/function scope (if more than one)
  • collapse if/else statements to expressions
  • minify conditional expressions to simpler ones
  • merge sequential expression statements to one, including into return and throw
  • remove superfluous grouping in expressions
  • shorten or remove string escapes
  • convert object key or index expression from string to identifier or decimal
  • merge concatenated strings
  • rewrite numbers (binary, octal, decimal, hexadecimal) to shorter representations

Options:

  • KeepVarNames keeps variable names as they are and omits shortening variable names
  • Precision number of significant digits to preserve for numbers, 0 means no trimming

Comparison with other tools

Performance is measured with time [command] ran 10 times and selecting the fastest one, on a Thinkpad T460 (i5-6300U quad-core 2.4GHz running Arch Linux) using Go 1.15.

  • minify: minify -o script.min.js script.js
  • esbuild: esbuild --minify --outfile=script.min.js script.js
  • terser: terser script.js --compress --mangle -o script.min.js
  • UglifyJS: uglifyjs --compress --mangle -o script.min.js script.js
  • Closure Compiler: closure-compiler -O SIMPLE --js script.js --js_output_file script.min.js --language_in ECMASCRIPT_NEXT -W QUIET --jscomp_off=checkVars optimization level SIMPLE instead of ADVANCED to make similar assumptions as do the other tools (do not rename/assume anything of global level variables)

Compression ratio (lower is better)

All tools give very similar results, although UglifyJS compresses slightly better.

Tool ace.js dot.js jquery.js jqueryui.js moment.js
minify 53.7% 64.8% 34.2% 51.3% 34.8%
esbuild 53.8% 66.3% 34.4% 53.1% 34.8%
terser 53.2% 65.2% 34.2% 51.8% 34.7%
UglifyJS 53.1% 64.7% 33.8% 50.7% 34.2%
Closure Compiler 53.4% 64.0% 35.7% 53.6% 34.3%

Time (lower is better)

Most tools are extremely slow, with minify and esbuild being orders of magnitudes faster.

Tool ace.js dot.js jquery.js jqueryui.js moment.js
minify 49ms 5ms 22ms 35ms 13ms
esbuild 64ms 9ms 31ms 51ms 17ms
terser 2900s 180ms 1400ms 2200ms 730ms
UglifyJS 3900ms 210ms 2000ms 3100ms 910ms
Closure Compiler 6100ms 2500ms 4400ms 5300ms 3500ms

JSON

Minification typically shaves off about 15% of filesize for common indented JSON such as generated by JSON Generator.

The JSON minifier only removes whitespace, which is the only thing that can be left out, and minifies numbers (1000 => 1e3).

Options:

  • Precision number of significant digits to preserve for numbers, 0 means no trimming

SVG

The SVG minifier uses these minifications:

  • trim and collapse whitespace between all tags
  • strip comments, empty doctype, XML prelude, metadata
  • strip SVG version
  • strip CDATA sections wherever possible
  • collapse tags with no content to a void tag
  • minify style tag and attributes with the CSS minifier
  • minify colors
  • shorten lengths and numbers and remove default px unit
  • shorten path data
  • use relative or absolute positions in path data whichever is shorter

TODO:

  • convert attributes to style attribute whenever shorter
  • merge path data? (same style and no intersection -- the latter is difficult)

Options:

  • Precision number of significant digits to preserve for numbers, 0 means no trimming

XML

The XML minifier uses these minifications:

  • strip unnecessary whitespace and otherwise collapse it to one space (or newline if it originally contained a newline)
  • strip comments
  • collapse tags with no content to a void tag
  • strip CDATA sections wherever possible

Options:

  • KeepWhitespace preserve whitespace between inline tags but still collapse multiple whitespace characters into one

Usage

Any input stream is being buffered by the minification functions. This is how the underlying buffer package inherently works to ensure high performance. The output stream however is not buffered. It is wise to preallocate a buffer as big as the input to which the output is written, or otherwise use bufio to buffer to a streaming writer.

New

Retrieve a minifier struct which holds a map of mediatype → minifier functions.

m := minify.New()

The following loads all provided minifiers.

m := minify.New()
m.AddFunc("text/css", css.Minify)
m.AddFunc("text/html", html.Minify)
m.AddFunc("image/svg+xml", svg.Minify)
m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)

You can set options to several minifiers.

m.Add("text/html", &html.Minifier{
	KeepDefaultAttrVals: true,
	KeepWhitespace: true,
})

From reader

Minify from an io.Reader to an io.Writer for a specific mediatype.

if err := m.Minify(mediatype, w, r); err != nil {
	panic(err)
}

From bytes

Minify from and to a []byte for a specific mediatype.

b, err = m.Bytes(mediatype, b)
if err != nil {
	panic(err)
}

From string

Minify from and to a string for a specific mediatype.

s, err = m.String(mediatype, s)
if err != nil {
	panic(err)
}

To reader

Get a minifying reader for a specific mediatype.

mr := m.Reader(mediatype, r)
if _, err := mr.Read(b); err != nil {
	panic(err)
}

To writer

Get a minifying writer for a specific mediatype. Must be explicitly closed because it uses an io.Pipe underneath.

mw := m.Writer(mediatype, w)
if mw.Write([]byte("input")); err != nil {
	panic(err)
}
if err := mw.Close(); err != nil {
	panic(err)
}

Middleware

Minify resources on the fly using middleware. It passes a wrapped response writer to the handler that removes the Content-Length header. The minifier is chosen based on the Content-Type header or, if the header is empty, by the request URI file extension. This is on-the-fly processing, you should preferably cache the results though!

fs := http.FileServer(http.Dir("www/"))
http.Handle("/", m.Middleware(fs))

Custom minifier

Add a minifier for a specific mimetype.

type CustomMinifier struct {
	KeepLineBreaks bool
}

func (c *CustomMinifier) Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
	// ...
	return nil
}

m.Add(mimetype, &CustomMinifier{KeepLineBreaks: true})
// or
m.AddRegexp(regexp.MustCompile("/x-custom$"), &CustomMinifier{KeepLineBreaks: true})

Add a minify function for a specific mimetype.

m.AddFunc(mimetype, func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
	// ...
	return nil
})
m.AddFuncRegexp(regexp.MustCompile("/x-custom$"), func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
	// ...
	return nil
})

Add a command cmd with arguments args for a specific mimetype.

m.AddCmd(mimetype, exec.Command(cmd, args...))
m.AddCmdRegexp(regexp.MustCompile("/x-custom$"), exec.Command(cmd, args...))

Mediatypes

Using the params map[string]string argument one can pass parameters to the minifier such as seen in mediatypes (type/subtype; key1=val2; key2=val2). Examples are the encoding or charset of the data. Calling Minify will split the mimetype and parameters for the minifiers for you, but MinifyMimetype can be used if you already have them split up.

Minifiers can also be added using a regular expression. For example a minifier with image/.* will match any image mime.

Examples

Common minifiers

Basic example that minifies from stdin to stdout and loads the default HTML, CSS and JS minifiers. Optionally, one can enable java -jar build/compiler.jar to run for JS (for example the ClosureCompiler). Note that reading the file into a buffer first and writing to a pre-allocated buffer would be faster (but would disable streaming).

package main

import (
	"log"
	"os"
	"os/exec"

	"github.com/tdewolff/minify/v2"
	"github.com/tdewolff/minify/v2/css"
	"github.com/tdewolff/minify/v2/html"
	"github.com/tdewolff/minify/v2/js"
	"github.com/tdewolff/minify/v2/json"
	"github.com/tdewolff/minify/v2/svg"
	"github.com/tdewolff/minify/v2/xml"
)

func main() {
	m := minify.New()
	m.AddFunc("text/css", css.Minify)
	m.AddFunc("text/html", html.Minify)
	m.AddFunc("image/svg+xml", svg.Minify)
	m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
	m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
	m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)

	if err := m.Minify("text/html", os.Stdout, os.Stdin); err != nil {
		panic(err)
	}
}

External minifiers

Below are some examples of using common external minifiers.

Closure Compiler

See Closure Compiler Application. Not tested.

m.AddCmdRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"),
    exec.Command("java", "-jar", "build/compiler.jar"))

UglifyJS

See UglifyJS.

m.AddCmdRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"),
    exec.Command("uglifyjs"))

esbuild

See esbuild.

m.AddCmdRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"),
    exec.Command("esbuild", "$in.js", "--minify", "--outfile=$out.js"))

Custom minifier

Custom minifier showing an example that implements the minifier function interface. Within a custom minifier, it is possible to call any minifier function (through m minify.Minifier) recursively when dealing with embedded resources.

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"strings"

	"github.com/tdewolff/minify/v2"
)

func main() {
	m := minify.New()
	m.AddFunc("text/plain", func(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
		// remove newlines and spaces
		rb := bufio.NewReader(r)
		for {
			line, err := rb.ReadString('\n')
			if err != nil && err != io.EOF {
				return err
			}
			if _, errws := io.WriteString(w, strings.Replace(line, " ", "", -1)); errws != nil {
				return errws
			}
			if err == io.EOF {
				break
			}
		}
		return nil
	})

	in := "Because my coffee was too cold, I heated it in the microwave."
	out, err := m.String("text/plain", in)
	if err != nil {
		panic(err)
	}
	fmt.Println(out)
	// Output: Becausemycoffeewastoocold,Iheateditinthemicrowave.
}

ResponseWriter

Middleware

func main() {
	m := minify.New()
	m.AddFunc("text/css", css.Minify)
	m.AddFunc("text/html", html.Minify)
	m.AddFunc("image/svg+xml", svg.Minify)
	m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
	m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
	m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)

	fs := http.FileServer(http.Dir("www/"))
	http.Handle("/", m.Middleware(fs))
}

ResponseWriter

func Serve(w http.ResponseWriter, r *http.Request) {
	mw := m.ResponseWriter(w, r)
	defer mw.Close()
	w = mw

	http.ServeFile(w, r, path.Join("www", r.URL.Path))
}

Custom response writer

ResponseWriter example which returns a ResponseWriter that minifies the content and then writes to the original ResponseWriter. Any write after applying this filter will be minified.

type MinifyResponseWriter struct {
	http.ResponseWriter
	io.WriteCloser
}

func (m MinifyResponseWriter) Write(b []byte) (int, error) {
	return m.WriteCloser.Write(b)
}

// MinifyResponseWriter must be closed explicitly by calling site.
func MinifyFilter(mediatype string, res http.ResponseWriter) MinifyResponseWriter {
	m := minify.New()
	// add minfiers

	mw := m.Writer(mediatype, res)
	return MinifyResponseWriter{res, mw}
}
// Usage
func(w http.ResponseWriter, req *http.Request) {
	w = MinifyFilter("text/html", w)
	if _, err := io.WriteString(w, "<p class="message"> This HTTP response will be minified. </p>"); err != nil {
		panic(err)
	}
	if err := w.Close(); err != nil {
		panic(err)
	}
	// Output: <p class=message>This HTTP response will be minified.
}

Templates

Here's an example of a replacement for template.ParseFiles from template/html, which automatically minifies each template before parsing it.

Be aware that minifying templates will work in most cases but not all. Because the HTML minifier only works for valid HTML5, your template must be valid HTML5 of itself. Template tags are parsed as regular text by the minifier.

func compileTemplates(filenames ...string) (*template.Template, error) {
	m := minify.New()
	m.AddFunc("text/html", html.Minify)

	var tmpl *template.Template
	for _, filename := range filenames {
		name := filepath.Base(filename)
		if tmpl == nil {
			tmpl = template.New(name)
		} else {
			tmpl = tmpl.New(name)
		}

		b, err := ioutil.ReadFile(filename)
		if err != nil {
			return nil, err
		}

		mb, err := m.Bytes("text/html", b)
		if err != nil {
			return nil, err
		}
		tmpl.Parse(string(mb))
	}
	return tmpl, nil
}

Example usage:

templates := template.Must(compileTemplates("view.html", "home.html"))

License

Released under the MIT license.

Comments
  • [Feature] Add NPM package with API for Node.js

    [Feature] Add NPM package with API for Node.js

    I'm creating a web-server in Node.js and at first I used a minifier written in JavaScript, which as you can guess had a poor performance. Looking for something better I found this and esbuild, esbuild was easy to integrate into my project since it has an NPM package exposing a module which I can import into Node.js which contains an API to use it with Node.js.

    All I have to do is to npm i esbuild and then for example:

    let esbuild = require('esbuild')
    let result1 = esbuild.transformSync(code, options)
    

    I would rather use your minifier though, so it would be nice it that could be made possible in the future.

  • Keep double quotes for hyper links

    Keep double quotes for hyper links

  • Results of testing against CDNjs

    Results of testing against CDNjs

    In process of testing this library, I've decided to run it against CDNjs repo (https://cdnjs.com/libraries) as a source of all the possible popular libraries, which makes it a decent test suite for JS/CSS tooling (I'm using it myself for other tools).

    I've narrowed the list to just JavaScript libraries, then only to latest version of each, and then to those that don't end with .min.js to avoid obviously pre-minified ones (and speed up testing).

    This way I've got 17,547 JavaScript files with a total weight of 771 MB.

    Then, I've run them all against latest minify binary, and then parsed with a full-blown ES5 parser to compare ASTs before and after transformation to detect semantic mismatches or syntactically broken files.

    From a first glance, most issues seem to be related to https://github.com/tdewolff/parse/issues/16, but there might be others as well so providing details below.


    This helped to reveal 30 files where semantic mismatch occured:

    ./ag-grid/9.0.0/ag-grid.js
    @@ -25789,9 +25789,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./angular-strap/2.3.12/modules/date-formatter.js
    @@ -12,9 +12,9 @@
             this.weekdaysShort = function (lang) {
                 return $locale.DATETIME_FORMATS.SHORTDAY;
             };
             function splitTimeFormat(format) {
    -            return /(h+)([:\.])?(m+)([:\.])?(s*)[ ]?(a?)/i.exec(format).slice(1);
    +            return /(h+)([:\.])?(m+)([:\.])?(s*)[]?(a?)/i.exec(format).slice(1);
             }
             this.hoursFormat = function (timeFormat) {
                 return splitTimeFormat(timeFormat)[0];
             };
    
    
    ./angular-strap/2.3.12/angular-strap.js
    @@ -3533,9 +3533,9 @@
                 this.weekdaysShort = function (lang) {
                     return $locale.DATETIME_FORMATS.SHORTDAY;
                 };
                 function splitTimeFormat(format) {
    -                return /(h+)([:\.])?(m+)([:\.])?(s*)[ ]?(a?)/i.exec(format).slice(1);
    +                return /(h+)([:\.])?(m+)([:\.])?(s*)[]?(a?)/i.exec(format).slice(1);
                 }
                 this.hoursFormat = function (timeFormat) {
                     return splitTimeFormat(timeFormat)[0];
                 };
    
    
    ./angular-strap/2.3.12/angular-strap.compat.js
    @@ -3533,9 +3533,9 @@
                 this.weekdaysShort = function (lang) {
                     return $locale.DATETIME_FORMATS.SHORTDAY;
                 };
                 function splitTimeFormat(format) {
    -                return /(h+)([:\.])?(m+)([:\.])?(s*)[ ]?(a?)/i.exec(format).slice(1);
    +                return /(h+)([:\.])?(m+)([:\.])?(s*)[]?(a?)/i.exec(format).slice(1);
                 }
                 this.hoursFormat = function (timeFormat) {
                     return splitTimeFormat(timeFormat)[0];
                 };
    
    
    ./bootstrap-validator/0.5.3/js/bootstrapValidator.js
    @@ -7452,11 +7452,11 @@
                     return true;
                 }
                 switch (true) {
                 case /^[0-9A-F]{15}$/i.test(value):
    -            case /^[0-9A-F]{2}[- ][0-9A-F]{6}[- ][0-9A-F]{6}[- ][0-9A-F]$/i.test(value):
    +            case /^[0-9A-F]{2}[-][0-9A-F]{6}[-][0-9A-F]{6}[-][0-9A-F]$/i.test(value):
                 case /^\d{19}$/.test(value):
    -            case /^\d{5}[- ]\d{5}[- ]\d{4}[- ]\d{4}[- ]\d$/.test(value):
    +            case /^\d{5}[-]\d{5}[-]\d{4}[-]\d{4}[-]\d$/.test(value):
                     var cd = value.charAt(value.length - 1);
                     value = value.replace(/[- ]/g, '');
                     if (value.match(/^\d*$/i)) {
                         return $.fn.bootstrapValidator.helpers.luhn(value);
    @@ -7471,11 +7471,11 @@
                         sum += parseInt(cdCalc.charAt(i), 16);
                     }
                     return sum % 10 === 0 ? cd === '0' : cd === ((Math.floor((sum + 10) / 10) * 10 - sum) * 2).toString(16);
                 case /^[0-9A-F]{14}$/i.test(value):
    -            case /^[0-9A-F]{2}[- ][0-9A-F]{6}[- ][0-9A-F]{6}$/i.test(value):
    +            case /^[0-9A-F]{2}[-][0-9A-F]{6}[-][0-9A-F]{6}$/i.test(value):
                 case /^\d{18}$/.test(value):
    -            case /^\d{5}[- ]\d{5}[- ]\d{4}[- ]\d{4}$/.test(value):
    +            case /^\d{5}[-]\d{5}[-]\d{4}[-]\d{4}$/.test(value):
                     return true;
                 default:
                     return false;
                 }
    
    
    ./card/2.2.1/jquery.card.js
    @@ -479,9 +479,9 @@
                             memo = fn.apply(this, arguments);
                         return memo;
                     };
                 }, isOldIE = memoize(function () {
    -                return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                 }), getHeadElement = memoize(function () {
                     return document.head || document.getElementsByTagName('head')[0];
                 }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
             module.exports = function (list, options) {
    
    
    ./card/2.2.1/card.js
    @@ -444,9 +444,9 @@
                             memo = fn.apply(this, arguments);
                         return memo;
                     };
                 }, isOldIE = memoize(function () {
    -                return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                 }), getHeadElement = memoize(function () {
                     return document.head || document.getElementsByTagName('head')[0];
                 }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
             module.exports = function (list, options) {
    
    
    ./fullcalendar/3.3.1/locale/es.js
    @@ -18,33 +18,33 @@
                     longDateFormat: {
                         LT: 'H:mm',
                         LTS: 'H:mm:ss',
                         L: 'DD/MM/YYYY',
    -                    LL: 'D [de] MMMM [de] YYYY',
    -                    LLL: 'D [de] MMMM [de] YYYY H:mm',
    -                    LLLL: 'dddd, D [de] MMMM [de] YYYY H:mm'
    +                    LL: 'D[de]MMMM[de]YYYY',
    +                    LLL: 'D[de]MMMM[de]YYYY H:mm',
    +                    LLLL: 'dddd,D[de]MMMM[de]YYYY H:mm'
                     },
                     calendar: {
                         sameDay: function () {
    -                        return '[hoy a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[hoy a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         nextDay: function () {
    -                        return '[mañana a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[mañana a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         nextWeek: function () {
    -                        return 'dddd [a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return 'dddd[a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         lastDay: function () {
    -                        return '[ayer a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[ayer a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         lastWeek: function () {
    -                        return '[el] dddd [pasado a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[el]dddd[pasado a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         sameElse: 'L'
                     },
                     relativeTime: {
    -                    future: 'en %s',
    -                    past: 'hace %s',
    +                    future: 'en%s',
    +                    past: 'hace%s',
                         s: 'unos segundos',
                         m: 'un minuto',
                         mm: '%d minutos',
                         h: 'una hora',
    
    
    ./fullcalendar/3.3.1/locale/es-do.js
    @@ -18,33 +18,33 @@
                     longDateFormat: {
                         LT: 'h:mm A',
                         LTS: 'h:mm:ss A',
                         L: 'DD/MM/YYYY',
    -                    LL: 'D [de] MMMM [de] YYYY',
    -                    LLL: 'D [de] MMMM [de] YYYY h:mm A',
    -                    LLLL: 'dddd, D [de] MMMM [de] YYYY h:mm A'
    +                    LL: 'D[de]MMMM[de]YYYY',
    +                    LLL: 'D[de]MMMM[de]YYYY h:mm A',
    +                    LLLL: 'dddd,D[de]MMMM[de]YYYY h:mm A'
                     },
                     calendar: {
                         sameDay: function () {
    -                        return '[hoy a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[hoy a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         nextDay: function () {
    -                        return '[mañana a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[mañana a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         nextWeek: function () {
    -                        return 'dddd [a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return 'dddd[a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         lastDay: function () {
    -                        return '[ayer a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[ayer a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         lastWeek: function () {
    -                        return '[el] dddd [pasado a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                        return '[el]dddd[pasado a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                         },
                         sameElse: 'L'
                     },
                     relativeTime: {
    -                    future: 'en %s',
    -                    past: 'hace %s',
    +                    future: 'en%s',
    +                    past: 'hace%s',
                         s: 'unos segundos',
                         m: 'un minuto',
                         mm: '%d minutos',
                         h: 'una hora',
    
    
    ./fullcalendar/3.3.1/locale-all.js
    @@ -2708,33 +2708,33 @@
                         longDateFormat: {
                             LT: 'H:mm',
                             LTS: 'H:mm:ss',
                             L: 'DD/MM/YYYY',
    -                        LL: 'D [de] MMMM [de] YYYY',
    -                        LLL: 'D [de] MMMM [de] YYYY H:mm',
    -                        LLLL: 'dddd, D [de] MMMM [de] YYYY H:mm'
    +                        LL: 'D[de]MMMM[de]YYYY',
    +                        LLL: 'D[de]MMMM[de]YYYY H:mm',
    +                        LLLL: 'dddd,D[de]MMMM[de]YYYY H:mm'
                         },
                         calendar: {
                             sameDay: function () {
    -                            return '[hoy a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[hoy a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             nextDay: function () {
    -                            return '[mañana a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[mañana a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             nextWeek: function () {
    -                            return 'dddd [a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return 'dddd[a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             lastDay: function () {
    -                            return '[ayer a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[ayer a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             lastWeek: function () {
    -                            return '[el] dddd [pasado a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[el]dddd[pasado a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             sameElse: 'L'
                         },
                         relativeTime: {
    -                        future: 'en %s',
    -                        past: 'hace %s',
    +                        future: 'en%s',
    +                        past: 'hace%s',
                             s: 'unos segundos',
                             m: 'un minuto',
                             mm: '%d minutos',
                             h: 'una hora',
    @@ -2846,33 +2846,33 @@
                         longDateFormat: {
                             LT: 'h:mm A',
                             LTS: 'h:mm:ss A',
                             L: 'DD/MM/YYYY',
    -                        LL: 'D [de] MMMM [de] YYYY',
    -                        LLL: 'D [de] MMMM [de] YYYY h:mm A',
    -                        LLLL: 'dddd, D [de] MMMM [de] YYYY h:mm A'
    +                        LL: 'D[de]MMMM[de]YYYY',
    +                        LLL: 'D[de]MMMM[de]YYYY h:mm A',
    +                        LLLL: 'dddd,D[de]MMMM[de]YYYY h:mm A'
                         },
                         calendar: {
                             sameDay: function () {
    -                            return '[hoy a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[hoy a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             nextDay: function () {
    -                            return '[mañana a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[mañana a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             nextWeek: function () {
    -                            return 'dddd [a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return 'dddd[a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             lastDay: function () {
    -                            return '[ayer a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[ayer a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             lastWeek: function () {
    -                            return '[el] dddd [pasado a la' + (1 !== this.hours() ? 's' : '') + '] LT';
    +                            return '[el]dddd[pasado a la' + (1 !== this.hours() ? 's' : '') + ']LT';
                             },
                             sameElse: 'L'
                         },
                         relativeTime: {
    -                        future: 'en %s',
    -                        past: 'hace %s',
    +                        future: 'en%s',
    +                        past: 'hace%s',
                             s: 'unos segundos',
                             m: 'un minuto',
                             mm: '%d minutos',
                             h: 'una hora',
    @@ -2982,27 +2982,27 @@
                     longDateFormat: {
                         LT: 'HH:mm',
                         LTS: 'HH:mm:ss',
                         L: 'YYYY-MM-DD',
    -                    LL: 'YYYY[ko] MMMM[ren] D[a]',
    -                    LLL: 'YYYY[ko] MMMM[ren] D[a] HH:mm',
    -                    LLLL: 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm',
    +                    LL: 'YYYY[ko]MMMM[ren]D[a]',
    +                    LLL: 'YYYY[ko]MMMM[ren]D[a]HH:mm',
    +                    LLLL: 'dddd,YYYY[ko]MMMM[ren]D[a]HH:mm',
                         l: 'YYYY-M-D',
    -                    ll: 'YYYY[ko] MMM D[a]',
    -                    lll: 'YYYY[ko] MMM D[a] HH:mm',
    -                    llll: 'ddd, YYYY[ko] MMM D[a] HH:mm'
    +                    ll: 'YYYY[ko]MMM D[a]',
    +                    lll: 'YYYY[ko]MMM D[a]HH:mm',
    +                    llll: 'ddd,YYYY[ko]MMM D[a]HH:mm'
                     },
                     calendar: {
    -                    sameDay: '[gaur] LT[etan]',
    -                    nextDay: '[bihar] LT[etan]',
    +                    sameDay: '[gaur]LT[etan]',
    +                    nextDay: '[bihar]LT[etan]',
                         nextWeek: 'dddd LT[etan]',
    -                    lastDay: '[atzo] LT[etan]',
    -                    lastWeek: '[aurreko] dddd LT[etan]',
    +                    lastDay: '[atzo]LT[etan]',
    +                    lastWeek: '[aurreko]dddd LT[etan]',
                         sameElse: 'L'
                     },
                     relativeTime: {
                         future: '%s barru',
    -                    past: 'duela %s',
    +                    past: 'duela%s',
                         s: 'segundo batzuk',
                         m: 'minutu bat',
                         mm: '%d minutu',
                         h: 'ordu bat',
    @@ -3135,9 +3135,9 @@
                             LTS: 'HH:mm:ss',
                             L: 'DD/MM/YYYY',
                             LL: 'D MMMM YYYY',
                             LLL: 'D MMMM YYYY HH:mm',
    -                        LLLL: 'dddd, D MMMM YYYY HH:mm'
    +                        LLLL: 'dddd,D MMMM YYYY HH:mm'
                         },
                         meridiemParse: /قبل از ظهر|بعد از ظهر/,
                         isPM: function (e) {
                             return /بعد از ظهر/.test(e);
    @@ -3145,17 +3145,17 @@
                         meridiem: function (e, a, t) {
                             return e < 12 ? 'قبل از ظهر' : 'بعد از ظهر';
                         },
                         calendar: {
    -                        sameDay: '[امروز ساعت] LT',
    -                        nextDay: '[فردا ساعت] LT',
    -                        nextWeek: 'dddd [ساعت] LT',
    -                        lastDay: '[دیروز ساعت] LT',
    -                        lastWeek: 'dddd [پیش] [ساعت] LT',
    +                        sameDay: '[امروز ساعت]LT',
    +                        nextDay: '[فردا ساعت]LT',
    +                        nextWeek: 'dddd[ساعت]LT',
    +                        lastDay: '[دیروز ساعت]LT',
    +                        lastWeek: 'dddd[پیش][ساعت]LT',
                             sameElse: 'L'
                         },
                         relativeTime: {
    -                        future: 'در %s',
    +                        future: 'در%s',
                             past: '%s پیش',
                             s: 'چندین ثانیه',
                             m: 'یک دقیقه',
                             mm: '%d دقیقه',
    @@ -3260,9 +3260,9 @@
                     list: 'برنامه'
                 },
                 allDayText: 'تمام روز',
                 eventLimitText: function (e) {
    -                return 'بیش از ' + e;
    +                return 'بیش از' + e;
                 },
                 noEventsMessage: 'هیچ رویدادی به نمایش'
             });
         }(), function () {
    @@ -3296,14 +3296,14 @@
                         return r ? 'vuoden' : 'vuosi';
                     case 'yy':
                         s = r ? 'vuoden' : 'vuotta';
                     }
    -                return s = t(e, r) + ' ' + s;
    +                return s = t(e, r) + '' + s;
                 }
                 function t(e, a) {
                     return e < 10 ? a ? r[e] : n[e] : e;
                 }
    -            var n = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '), r = [
    +            var n = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(''), r = [
                         'nolla',
                         'yhden',
                         'kahden',
                         'kolmen',
    @@ -3322,22 +3322,22 @@
                         longDateFormat: {
                             LT: 'HH.mm',
                             LTS: 'HH.mm.ss',
                             L: 'DD.MM.YYYY',
    -                        LL: 'Do MMMM[ta] YYYY',
    -                        LLL: 'Do MMMM[ta] YYYY, [klo] HH.mm',
    -                        LLLL: 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
    +                        LL: 'Do MMMM[ta]YYYY',
    +                        LLL: 'Do MMMM[ta]YYYY,[klo]HH.mm',
    +                        LLLL: 'dddd,Do MMMM[ta]YYYY,[klo]HH.mm',
                             l: 'D.M.YYYY',
                             ll: 'Do MMM YYYY',
    -                        lll: 'Do MMM YYYY, [klo] HH.mm',
    -                        llll: 'ddd, Do MMM YYYY, [klo] HH.mm'
    +                        lll: 'Do MMM YYYY,[klo]HH.mm',
    +                        llll: 'ddd,Do MMM YYYY,[klo]HH.mm'
                         },
                         calendar: {
    -                        sameDay: '[tänään] [klo] LT',
    -                        nextDay: '[huomenna] [klo] LT',
    -                        nextWeek: 'dddd [klo] LT',
    -                        lastDay: '[eilen] [klo] LT',
    -                        lastWeek: '[viime] dddd[na] [klo] LT',
    +                        sameDay: '[tänään][klo]LT',
    +                        nextDay: '[huomenna][klo]LT',
    +                        nextWeek: 'dddd[klo]LT',
    +                        lastDay: '[eilen][klo]LT',
    +                        lastWeek: '[viime]dddd[na][klo]LT',
                             sameElse: 'L'
                         },
                         relativeTime: {
                             future: '%s päästä',
    @@ -4133,17 +4133,17 @@
                             LT: 'A h:mm बजे',
                             LTS: 'A h:mm:ss बजे',
                             L: 'DD/MM/YYYY',
                             LL: 'D MMMM YYYY',
    -                        LLL: 'D MMMM YYYY, A h:mm बजे',
    -                        LLLL: 'dddd, D MMMM YYYY, A h:mm बजे'
    +                        LLL: 'D MMMM YYYY,A h:mm बजे',
    +                        LLLL: 'dddd,D MMMM YYYY,A h:mm बजे'
                         },
                         calendar: {
    -                        sameDay: '[आज] LT',
    -                        nextDay: '[कल] LT',
    -                        nextWeek: 'dddd, LT',
    -                        lastDay: '[कल] LT',
    -                        lastWeek: '[पिछले] dddd, LT',
    +                        sameDay: '[आज]LT',
    +                        nextDay: '[कल]LT',
    +                        nextWeek: 'dddd,LT',
    +                        lastDay: '[कल]LT',
    +                        lastWeek: '[पिछले]dddd,LT',
                             sameElse: 'L'
                         },
                         relativeTime: {
                             future: '%s में',
    @@ -4188,16 +4188,16 @@
                 prevText: 'पिछला',
                 nextText: 'अगला',
                 currentText: 'आज',
                 monthNames: [
    -                'जनवरी ',
    +                'जनवरी',
                     'फरवरी',
                     'मार्च',
                     'अप्रेल',
                     'मई',
                     'जून',
                     'जूलाई',
    -                'अगस्त ',
    +                'अगस्त',
                     'सितम्बर',
                     'अक्टूबर',
                     'नवम्बर',
                     'दिसम्बर'
    @@ -4257,16 +4257,16 @@
                     list: 'कार्यसूची'
                 },
                 allDayText: 'सभी दिन',
                 eventLimitText: function (e) {
    -                return '+अधिक ' + e;
    +                return '+अधिक' + e;
                 },
                 noEventsMessage: 'कोई घटनाओं को प्रदर्शित करने के लिए'
             });
         }(), function () {
             !function () {
                 function e(e, a, t) {
    -                var n = e + ' ';
    +                var n = e + '';
                     switch (t) {
                     case 'm':
                         return a ? 'jedna minuta' : 'jedne minute';
                     case 'mm':
    @@ -4297,50 +4297,50 @@
                     longDateFormat: {
                         LT: 'H:mm',
                         LTS: 'H:mm:ss',
                         L: 'DD.MM.YYYY',
    -                    LL: 'D. MMMM YYYY',
    -                    LLL: 'D. MMMM YYYY H:mm',
    -                    LLLL: 'dddd, D. MMMM YYYY H:mm'
    +                    LL: 'D.MMMM YYYY',
    +                    LLL: 'D.MMMM YYYY H:mm',
    +                    LLLL: 'dddd,D.MMMM YYYY H:mm'
                     },
                     calendar: {
    -                    sameDay: '[danas u] LT',
    -                    nextDay: '[sutra u] LT',
    +                    sameDay: '[danas u]LT',
    +                    nextDay: '[sutra u]LT',
                         nextWeek: function () {
                             switch (this.day()) {
                             case 0:
    -                            return '[u] [nedjelju] [u] LT';
    +                            return '[u][nedjelju][u]LT';
                             case 3:
    -                            return '[u] [srijedu] [u] LT';
    +                            return '[u][srijedu][u]LT';
                             case 6:
    -                            return '[u] [subotu] [u] LT';
    +                            return '[u][subotu][u]LT';
                             case 1:
                             case 2:
                             case 4:
                             case 5:
    -                            return '[u] dddd [u] LT';
    +                            return '[u]dddd[u]LT';
                             }
                         },
    -                    lastDay: '[jučer u] LT',
    +                    lastDay: '[jučer u]LT',
                         lastWeek: function () {
                             switch (this.day()) {
                             case 0:
                             case 3:
    -                            return '[prošlu] dddd [u] LT';
    +                            return '[prošlu]dddd[u]LT';
                             case 6:
    -                            return '[prošle] [subote] [u] LT';
    +                            return '[prošle][subote][u]LT';
                             case 1:
                             case 2:
                             case 4:
                             case 5:
    -                            return '[prošli] dddd [u] LT';
    +                            return '[prošli]dddd[u]LT';
                             }
                         },
                         sameElse: 'L'
                     },
                     relativeTime: {
    -                    future: 'za %s',
    -                    past: 'prije %s',
    +                    future: 'za%s',
    +                    past: 'prije%s',
                         s: 'par sekundi',
                         m: e,
                         mm: e,
                         h: e,
    
    
    ./graphicsjs/1.2.0/graphics.js
    @@ -4719,15 +4719,15 @@
         if (goog.userAgent.EDGE) {
             return /Edge\/([\d\.]+)/.exec(userAgent);
         }
         if (goog.userAgent.IE) {
    -        return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
    +        return /\b(?:MSIE|rv)[:]([^\);]+)(\)|;)/.exec(userAgent);
         }
         if (goog.userAgent.WEBKIT) {
             return /WebKit\/(\S+)/.exec(userAgent);
         }
         if (goog.userAgent.OPERA) {
    -        return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
    +        return /(?:Version)[\/]?(\S+)/.exec(userAgent);
         }
         return undefined;
     };
     goog.userAgent.getDocumentMode_ = function () {
    
    
    ./geojs/0.11.1/geo.js
    @@ -5854,9 +5854,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(self.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./ionic/1.3.2/js/ionic-angular.js
    @@ -2545,9 +2545,9 @@
                             return applyIOS9Shim($delegate);
                         }
                         return $delegate;
                         function isIOS9UIWebView(userAgent) {
    -                        return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
    +                        return /(iPhone|iPad|iPod).*OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
                         }
                         function applyIOS9Shim(browser) {
                             var pendingLocationUrl = null;
                             var originalUrlFn = browser.url;
    
    
    ./ionic/1.3.2/js/ionic.bundle.js
    @@ -28007,9 +28007,9 @@
                             return applyIOS9Shim($delegate);
                         }
                         return $delegate;
                         function isIOS9UIWebView(userAgent) {
    -                        return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
    +                        return /(iPhone|iPad|iPod).*OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
                         }
                         function applyIOS9Shim(browser) {
                             var pendingLocationUrl = null;
                             var originalUrlFn = browser.url;
    
    
    ./jquery.bootstrapvalidator/0.5.3/js/bootstrapValidator.js
    @@ -7452,11 +7452,11 @@
                     return true;
                 }
                 switch (true) {
                 case /^[0-9A-F]{15}$/i.test(value):
    -            case /^[0-9A-F]{2}[- ][0-9A-F]{6}[- ][0-9A-F]{6}[- ][0-9A-F]$/i.test(value):
    +            case /^[0-9A-F]{2}[-][0-9A-F]{6}[-][0-9A-F]{6}[-][0-9A-F]$/i.test(value):
                 case /^\d{19}$/.test(value):
    -            case /^\d{5}[- ]\d{5}[- ]\d{4}[- ]\d{4}[- ]\d$/.test(value):
    +            case /^\d{5}[-]\d{5}[-]\d{4}[-]\d{4}[-]\d$/.test(value):
                     var cd = value.charAt(value.length - 1);
                     value = value.replace(/[- ]/g, '');
                     if (value.match(/^\d*$/i)) {
                         return $.fn.bootstrapValidator.helpers.luhn(value);
    @@ -7471,11 +7471,11 @@
                         sum += parseInt(cdCalc.charAt(i), 16);
                     }
                     return sum % 10 === 0 ? cd === '0' : cd === ((Math.floor((sum + 10) / 10) * 10 - sum) * 2).toString(16);
                 case /^[0-9A-F]{14}$/i.test(value):
    -            case /^[0-9A-F]{2}[- ][0-9A-F]{6}[- ][0-9A-F]{6}$/i.test(value):
    +            case /^[0-9A-F]{2}[-][0-9A-F]{6}[-][0-9A-F]{6}$/i.test(value):
                 case /^\d{18}$/.test(value):
    -            case /^\d{5}[- ]\d{5}[- ]\d{4}[- ]\d{4}$/.test(value):
    +            case /^\d{5}[-]\d{5}[-]\d{4}[-]\d{4}$/.test(value):
                     return true;
                 default:
                     return false;
                 }
    
    
    ./lovefield/2.1.12/lovefield.es6.js
    @@ -754,13 +754,13 @@
                         return /rv\:([^\);]+)(\)|;)/.exec(a);
                     if (Ib)
                         return /Edge\/([\d\.]+)/.exec(a);
                     if (Hb)
    -                    return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);
    +                    return /\b(?:MSIE|rv)[:]([^\);]+)(\)|;)/.exec(a);
                     if (Kb)
                         return /WebKit\/(\S+)/.exec(a);
                     if (Gb)
    -                    return /(?:Version)[ \/]?(\S+)/.exec(a);
    +                    return /(?:Version)[\/]?(\S+)/.exec(a);
                 }();
             Tb && (Sb = Tb ? Tb[1] : '');
             if (Hb) {
                 var Ub, Vb = da.document;
    
    
    ./lovefield/2.1.12/lovefield.js
    @@ -3839,15 +3839,15 @@
             if (goog.userAgent.EDGE) {
                 return /Edge\/([\d\.]+)/.exec(userAgent);
             }
             if (goog.userAgent.IE) {
    -            return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
    +            return /\b(?:MSIE|rv)[:]([^\);]+)(\)|;)/.exec(userAgent);
             }
             if (goog.userAgent.WEBKIT) {
                 return /WebKit\/(\S+)/.exec(userAgent);
             }
             if (goog.userAgent.OPERA) {
    -            return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
    +            return /(?:Version)[\/]?(\S+)/.exec(userAgent);
             }
         };
         goog.userAgent.getDocumentMode_ = function () {
             var doc = goog.global.document;
    
    
    ./polythene/0.2.35/polythene-standalone.js
    @@ -89,9 +89,9 @@
                         s.e(type.call(t) == ARRAY ? t.map(function (e) {
                             return e.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, s.l);
                         }).join(' ') : t.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, s.l), e);
                     } else
    -                    /^@(?:font-face$|viewport$|page )/.test(e) ? sheet(t, n, e, e, emptyArray) : /^@global$/.test(e) ? sheet(t, n, r, a, l, 0, s) : /^@local$/.test(e) ? sheet(t, n, r, a, l, 1, s) : /^@(?:media |supports |document )./.test(e) ? (n.push(e, ' {\n'), sheet(t, n, r, a, l, o, s), n.push('}\n')) : n.push('@-error-unsupported-at-rule ', JSON.stringify(e), ';\n');
    +                    /^@(?:font-face$|viewport$|page)/.test(e) ? sheet(t, n, e, e, emptyArray) : /^@global$/.test(e) ? sheet(t, n, r, a, l, 0, s) : /^@local$/.test(e) ? sheet(t, n, r, a, l, 1, s) : /^@(?:media |supports |document )./.test(e) ? (n.push(e, ' {\n'), sheet(t, n, r, a, l, o, s), n.push('}\n')) : n.push('@-error-unsupported-at-rule ', JSON.stringify(e), ';\n');
                 }
                 function sheet(e, t, n, r, a, l, o) {
                     var s, c, i, u;
                     switch (type.call(e)) {
    
    
    ./qooxdoo/5.0/q.js
    @@ -7195,9 +7195,9 @@
                     ;
                     return 'desktop';
                 },
                 detectMobileDevice: function (userAgentString) {
    -                return /android.+mobile|ip(hone|od)|bada\/|blackberry|BB10|maemo|opera m(ob|in)i|fennec|NetFront|phone|psp|symbian|IEMobile|windows (ce|phone)|xda/i.test(userAgentString);
    +                return /android.+mobile|ip(hone|od)|bada\/|blackberry|BB10|maemo|opera m(ob|in)i|fennec|NetFront|phone|psp|symbian|IEMobile|windows(ce|phone)|xda/i.test(userAgentString);
                 },
                 detectTabletDevice: function (userAgentString) {
                     var isIE10Tablet = /MSIE 10/i.test(userAgentString) && /ARM/i.test(userAgentString) && !/windows phone/i.test(userAgentString);
                     var isCommonTablet = !/android.+mobile|Tablet PC/i.test(userAgentString) && /Android|ipad|tablet|playbook|silk|kindle|psp/i.test(userAgentString);
    
    
    ./react-data-grid/2.0.24/react-data-grid.js
    @@ -94,9 +94,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(self.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./soundplayer-widget/0.4.1/soundplayer-widget.js
    @@ -136,9 +136,9 @@
                             memo = fn.apply(this, arguments);
                         return memo;
                     };
                 }, isOldIE = memoize(function () {
    -                return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                 }), getHeadElement = memoize(function () {
                     return document.head || document.getElementsByTagName('head')[0];
                 }), singletonElement = null, singletonCounter = 0;
             module.exports = function (list, options) {
    
    
    ./swagger-ui/3.0.4/swagger-ui-standalone-preset.js
    @@ -1253,9 +1253,9 @@
                         return function () {
                             return 'undefined' == typeof t && (t = e.apply(this, arguments)), t;
                         };
                     }, b = g(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), m = g(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), x = null, w = 0, h = [];
                 e.exports = function (e, t) {
    
    
    ./tablesort/5.0.0/src/sorts/tablesort.filesize.js
    @@ -35,9 +35,9 @@
                 var num = parseFloat(cleanNumber(matches[1])), suffix = matches[3];
                 return num * suffix2num(suffix);
             };
         Tablesort.extend('filesize', function (item) {
    -        return /^\d+(\.\d+)? ?(K|M|G|T|P|E|Z|Y|B$)i?B?$/i.test(item);
    +        return /^\d+(\.\d+)??(K|M|G|T|P|E|Z|Y|B$)i?B?$/i.test(item);
         }, function (a, b) {
             a = filesize2num(a);
             b = filesize2num(b);
             return compareNumber(b, a);
    
    
    ./tota11y/0.1.6/tota11y.js
    @@ -5892,9 +5892,9 @@
                             memo = fn.apply(this, arguments);
                         return memo;
                     };
                 }, isOldIE = memoize(function () {
    -                return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                 }), getHeadElement = memoize(function () {
                     return document.head || document.getElementsByTagName('head')[0];
                 }), singletonElement = null, singletonCounter = 0;
             module.exports = function (list, options) {
    
    
    ./swagger-ui/3.0.4/swagger-ui-bundle.js
    @@ -37377,9 +37377,9 @@
                         return function () {
                             return 'undefined' == typeof t && (t = e.apply(this, arguments)), t;
                         };
                     }, m = d(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), v = d(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), y = null, g = 0, _ = [];
                 e.exports = function (e, t) {
    
    
    ./vue-color/2.0.7/vue-color.js
    @@ -202,9 +202,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./vue-google-maps/0.1.21/vue-google-maps.js
    @@ -3313,9 +3313,9 @@
                         return function () {
                             return 'undefined' == typeof e && (e = t.apply(this, arguments)), e;
                         };
                     }, y = h(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), v = h(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), g = null, m = 0, _ = [];
                 t.exports = function (t, e) {
    
    
    ./vue-smart-table/2.5.0/vue-smart-table.js
    @@ -842,9 +842,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./vue-strap/1.1.37/vue-strap.js
    @@ -1865,9 +1865,9 @@
                                 memo = fn.apply(this, arguments);
                             return memo;
                         };
                     }, isOldIE = memoize(function () {
    -                    return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
    +                    return /msie[6-9]\b/.test(window.navigator.userAgent.toLowerCase());
                     }), getHeadElement = memoize(function () {
                         return document.head || document.getElementsByTagName('head')[0];
                     }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [];
                 module.exports = function (list, options) {
    
    
    ./webshim/1.16.0/minified/shims/moxie/js/moxie-html4.js
    @@ -2736,12 +2736,12 @@
                         4: 'Flash',
                         9: 'Fine weather',
                         10: 'Cloudy weather',
                         11: 'Shade',
    -                    12: 'Daylight fluorescent (D 5700 - 7100K)',
    -                    13: 'Day white fluorescent (N 4600 -5400K)',
    -                    14: 'Cool white fluorescent (W 3900 - 4500K)',
    -                    15: 'White fluorescent (WW 3200 - 3700K)',
    +                    12: 'Daylight fluorescent(D 5700-7100K)',
    +                    13: 'Day white fluorescent(N 4600-5400K)',
    +                    14: 'Cool white fluorescent(W 3900-4500K)',
    +                    15: 'White fluorescent(WW 3200-3700K)',
                         17: 'Standard light A',
                         18: 'Standard light B',
                         19: 'Standard light C',
                         20: 'D55',
    @@ -2755,26 +2755,26 @@
                         0: 'Flash did not fire.',
                         1: 'Flash fired.',
                         5: 'Strobe return light not detected.',
                         7: 'Strobe return light detected.',
    -                    9: 'Flash fired, compulsory flash mode',
    -                    13: 'Flash fired, compulsory flash mode, return light not detected',
    -                    15: 'Flash fired, compulsory flash mode, return light detected',
    -                    16: 'Flash did not fire, compulsory flash mode',
    -                    24: 'Flash did not fire, auto mode',
    -                    25: 'Flash fired, auto mode',
    -                    29: 'Flash fired, auto mode, return light not detected',
    -                    31: 'Flash fired, auto mode, return light detected',
    +                    9: 'Flash fired,compulsory flash mode',
    +                    13: 'Flash fired,compulsory flash mode,return light not detected',
    +                    15: 'Flash fired,compulsory flash mode,return light detected',
    +                    16: 'Flash did not fire,compulsory flash mode',
    +                    24: 'Flash did not fire,auto mode',
    +                    25: 'Flash fired,auto mode',
    +                    29: 'Flash fired,auto mode,return light not detected',
    +                    31: 'Flash fired,auto mode,return light detected',
                         32: 'No flash function',
    -                    65: 'Flash fired, red-eye reduction mode',
    -                    69: 'Flash fired, red-eye reduction mode, return light not detected',
    -                    71: 'Flash fired, red-eye reduction mode, return light detected',
    -                    73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
    -                    77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
    -                    79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
    -                    89: 'Flash fired, auto mode, red-eye reduction mode',
    -                    93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
    -                    95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
    +                    65: 'Flash fired,red-eye reduction mode',
    +                    69: 'Flash fired,red-eye reduction mode,return light not detected',
    +                    71: 'Flash fired,red-eye reduction mode,return light detected',
    +                    73: 'Flash fired,compulsory flash mode,red-eye reduction mode',
    +                    77: 'Flash fired,compulsory flash mode,red-eye reduction mode,return light not detected',
    +                    79: 'Flash fired,compulsory flash mode,red-eye reduction mode,return light detected',
    +                    89: 'Flash fired,auto mode,red-eye reduction mode',
    +                    93: 'Flash fired,auto mode,return light not detected,red-eye reduction mode',
    +                    95: 'Flash fired,auto mode,return light detected,red-eye reduction mode'
                     },
                     ExposureMode: {
                         0: 'Auto exposure',
                         1: 'Manual exposure',
    
    

    And 20 files that were syntactically broken by the transformation:

    ./ace/1.2.6/mode-mask.js
    Unterminated string constant (1:39865)
    
    ./axios/0.16.0/axios.js
    Expecting Unicode escape sequence \uXXXX (84:633)
    
    ./booking-js/1.13.0/booking.js
    Expecting Unicode escape sequence \uXXXX (1463:673)
    
    ./github-api/3.0.0/GitHub.bundle.js
    Unterminated regular expression (142:519)
    
    ./html-minifier/3.4.2/htmlminifier.js
    Unterminated regular expression (1516:30)
    
    ./js-data-http/2.2.2/js-data-http.js
    Expecting Unicode escape sequence \uXXXX (105:673)
    
    ./mercury/14.1.0/mercury.js
    Unexpected token (846:29)
    
    ./metro/3.0.17/js/metro.js
    Expecting Unicode escape sequence \uXXXX (480:389)
    
    ./pannellum/2.3.2/pannellum.js
    Error parsing regular expression: Invalid regular expression: /^(?:[a-z]+:)?\/\A,!1),l.addEventListener("fullscreenchange",A,!1),z.addEventListener("resize",ga,!1),z.addEventListener("orientationchange",ga,!1),l.addEventListener("keydown",wa,!1),l.addEventListener("keyup",la,!1),l.addEventListener("blur",R,!1),k.addEventListener("mouseleave",fa,!1),F.addEventListener("touchstart",va,!1),F.addEventListener("touchmove",aa,!1),F.addEventListener("touchend",v,!1),F.addEventListener("pointerdown",d,!1),F.addEventListener("pointermove",a,!1),F.addEventListener("pointerup",K,!1),F.addEventListener("pointerleave",K,!1),z.navigator.pointerEnabled&&(l.style.touchAction="none"));f();setTimeout(function(){},500)}function $(a){var m=new FileReader;m.addEventListener("loadend",function(){var b=m.result;if(navigator.userAgent.toLowerCase().match(/: Unmatched ')' (3:15298)
    
    ./plastiq/1.14.0/plastiq.js
    Unexpected token (198:29)
    
    ./rasterizehtml/1.2.4/rasterizeHTML.allinone.js
    Unterminated string constant (3:70815)
    
    ./raven.js/3.14.0/raven.js
    Unexpected token (189:127)
    
    ./redux-persist/4.6.0/redux-persist.js
    Unexpected token (62:77)
    
    ./sp-pnp-js/2.0.2/pnp.js
    Unterminated regular expression (18:263)
    
    ./stylus/0.32.1/stylus.js
    Unterminated regular expression (389:112)
    
    ./stellar-sdk/0.7.3/stellar-sdk.js
    Expecting Unicode escape sequence \uXXXX (735:673)
    
    ./tern/0.21.0/plugin/node_resolve.js
    Unterminated regular expression (29:47)
    
    ./tern/0.21.0/plugin/modules.js
    Unterminated regular expression (100:33)
    
    ./timekit-js-sdk/1.6.1/timekit-sdk.js
    Expecting Unicode escape sequence \uXXXX (180:673)
    
    ./z-schema/3.18.2/ZSchema-browser.js
    Unexpected token (555:245)
    

    Overall, I'd say this is quite an impressive result compared to total number of tested files, but thought it might be interesting to look through these cases and see if something can be done about them (at least throwing a parsing error instead of breaking the content when unsure).

  • Recursive option with in-place output causes all files to be blank

    Recursive option with in-place output causes all files to be blank

    The following command causes all files to be blank after the operation completes:

    minify -r -o . .

    This occurs in subfolders only. When running the same command in the subfolder all files minify properly in-place.

  • symlinks not synced

    symlinks not synced

    I'm using minify as

    minify --match '\.(css|html|js|json)$' -sar dev/ -o prod/
    

    The match files are getting minified, the other files are getting synced, but the symlinks are been ignored and my prod is getting non functional as a result. There is a way to sync symlinks as well?

  • undefined: minify.Bytes

    undefined: minify.Bytes

    I am trying to get this module for one of my project as one of the dependency using "go get" command. I am facing issue while building my project using the source of minify with below error: $ go get github.com/tdewolff/minify cd /root/meghali/couchbase/ns_server/deps/gocode/src/minify /root/meghali/go/pkg/tool/linux_ppc64le/compile -o $WORK/minify.a -trimpath $WORK -p main -complete -buildid 68084043dd0f5163b57f1f669cd711eb912a1bce -D _/root/meghali/couchbase/ns_server/deps/gocode/src/minify -I $WORK -I /root/meghali/couchbase/build/gopkg/go-1.8.1 -pack ./minify.go

    minify

    ../../../deps/gocode/src/minify/minify.go:153: undefined: minify.Bytes

    The go version I am using is :1.8.1 Has anyone seen this issue before? Any pointers would be helpful.

    Thanks, Meghali

  • HTML minification is working probably, discussing template minification

    HTML minification is working probably, discussing template minification

    Hi there,

    I implemeted the minifier with the template function replacement. I am seeing some unexpected behaviour:

    Un-minified:

    <!DOCTYPE html>
    <html lang="en" dir="ltr">
    
    <head>
        <meta charset="utf-8" />
        <base href="http://127.0.0.1/" />
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
        <meta name="description" content="" />
    
        <meta property="og:description" content="" />
        <meta property="og:title" content="Home" />
        <meta property="og:type" content="website" />
        <meta property="og:url" content="http://127.0.0.1/" />
        <meta property="og:image" content="" />
    
        <title>Home</title>
    
        <link rel="stylesheet" href="/css/app.min.css" />
    </head>
    
    <body data-barba="wrapper">
        <script src="/js/app.min.js" defer></script>
    </body>
    
    </html>
    

    becomes this after minification:

    <!DOCTYPE html>
    
    <html lang="en" dir="ltr">
    
    
    <head>
    
       
    <meta charset="utf-8" />
    
       
    <base href="http://127.0.0.1/" />
    
       
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    
       
    <meta name="description" content="" />
    
    
       
    <meta property="og:description" content="" />
    
       
    <meta property="og:title" content="Home" />
    
       
    <meta property="og:type" content="website" />
    
       
    <meta property="og:url" content="http://127.0.0.1/" />
    
       
    <meta property="og:image" content="" />
    
    
       
    <title>Home</title>
    
    
       
    <link rel="stylesheet" href="/css/app.min.css" />
    
    </head>
    
    
    <body data-barba="wrapper">
    
    <script src="/js/app.min.js" defer></script>
    
    </body>
    
    
    </html>
    

    The file size goes up by 3 bytes.

    Is this intended behaviour, is there something wrong with the minifier or did I implement this in a false way?

    Thank you for your feedback, cheers!

  • IE7 star syntax gets broken

    IE7 star syntax gets broken

    I'm almost feeling bad for reporting an issue related to such extremely outdated browser, but apparently, these hacks are still popular as part of various frameworks that can get broken when minified using this tool.

    Basically, there are popular hacks for old IE versions that are often used to override certain properties only for them (described in e.g. https://learntech.imsu.ox.ac.uk/blog/?p=494).

    Example:

    p {
      color: red;    /* all browsers */
      *color: blue;  /* IE7 and below */
      _color: green; /* IE6 and below */
    }
    

    As we can see, after minification the _ hacks gets treated correctly, but * one breaks CSS:

    p{color:red*color:blue;_color:green}
    

    Moreover, if * was coming as the last property and wasn't followed by a semicolon

    p {
      color: red;
      *color: blue
    }
    
    div {
      color: green;
    }
    

    it breaks the following ruleset as well:

    p{color:red*color:bluediv;color:green}
    
  • HTML Minifer invalidates certain meta tags

    HTML Minifer invalidates certain meta tags

    Great library but I found an edge case.

    The Open Graph Protocol specifies HTML Meta tags for describing attributes of a webpage and is used by Facebook, Twitter, LinkedIn, etc. for LinkedIn, for content preview for articles, links, etc.

    After applying the default HTML minimizer to the following code LinkedIn was unable to parse meta tags in this format:

    <meta name="type" property="og:type" content="website" />
    

    Disabling the minifier solved the problem.

  • Possible CSS URI issue

    Possible CSS URI issue

    Hello, thanks for much for working minify and the matching parsers!

    In CSS, the URI minifier seems to picking the wrong format to get smallest output

    Here's a reduced test case:

    $ date
    Sun Mar  4 12:14:15 PST 2018
    
    $ go get -u github.com/tdewolff/minify/cmd/minify
    
    $ cat test.css
    p{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}
    
    $ minifiy test.css > test-out.css
    
    $ cat test-out.css
    p{background-image:url(data:image/svg+xml;charset=utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIHZpZXdCb3g9IjAgMCA4IDgiPjxwYXRoIGQ9Ik0yLjc1LjBsLTEuNSAxLjVMMy43NSA0bC0yLjUgMi41TDIuNzUgOGw0LTQtNC00eiIvPjwvc3ZnPg==)}
    
    $ ls -l test.css test-out.css 
    -rw-r--r--  1 nickg  staff  248 Mar  4 12:03 test-out.css
    -rw-r--r--  1 nickg  staff  211 Mar  4 12:01 test.css
    
    $ gzip -9 test.css test-out.css 
    
    $ ls -l test.css.gz test-out.css.gz 
    -rw-r--r--  1 nickg  staff  251 Mar  4 12:03 test-out.css.gz
    -rw-r--r--  1 nickg  staff  195 Mar  4 12:01 test.css.gz
    

    In addition, a flag to prevent base64 encoding might be wise. I've found that base64 encoding might make an absolute file smaller, but it also wrecks compression. In other words, the larger original format sometimes compresses better than the smaller base64 version.

    It is also highly possible there is user error in my analysis. Thoughts welcome and thanks again.

    n

  • <html> and <head> tags are crucial

    and tags are crucial

    Hi Taco – If Minify removes <html> and <head> tags, it will break many, perhaps most, websites.

    I know that the HTML spec describes those tags as optional, but they're not really optional on the modern web, for at least two reasons:

    Those elements often have attached attributes. For example, here's the code from the page we're on right now (https://github.com/tdewolff/minify):

    <html lang="en" class=" is-copy-enabled is-u2f-enabled">
      <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
    

    There are language tags on the HTML element, as well as a class, and then Open Graph metadata in the head tag, which is common. (Sometimes the og: metadata is in the HTML tag.) The metadata helps with FB and SEO.

    The second reason is that the <head> element contains lots of metadata that is not consumed by the browser, but rather by social networks and search. There are a bunch of FB and Twitter lines, Dublin Core, and now Schema.org. It's important to note that the browser isn't the only consumer of the HTML. All that new metadata is for formatting links and cards on Twitter and FB when users link to a page. Those systems expect to find the metadata in the head element. It's not clear what they would do if there is no head. It would be crazy to delete those elements without thoroughly testing how it impacts the Twitter Card and similar metadata systems.

    Also, deleting attribute quotes is a bad idea for security because it makes it much easier for Cross Site Scripting and other attacks to break out of the attribute value. This is mostly an issue when accepting untrusted input from users, like comments, but consistency in all markup is a good idea.

  • Bump node-api-headers from 0.0.1 to 0.0.2 in /bindings/js

    Bump node-api-headers from 0.0.1 to 0.0.2 in /bindings/js

    Bumps node-api-headers from 0.0.1 to 0.0.2.

    Release notes

    Sourced from node-api-headers's releases.

    Release 0.0.2

    SemVer patch release to pull changes since the last release. The main motivations for the release is to fix the wrong list of symbols for Node-API v6.

    What's Changed

    Full Changelog: https://github.com/nodejs/node-api-headers/compare/v0.0.1...v0.0.2

    Changelog

    Sourced from node-api-headers's changelog.

    2022-12-29 Version 0.0.2, @​NickNaso

    Notable changes

    • Fixed wrong symblos in v6.

    Commits

    • [9c0b4ecaa5] - fix: wrong symblos in v6 (#6) (Nicola Del Gobbo)
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • Use ESM or CommonJS import for JS binding

    Use ESM or CommonJS import for JS binding

    According to https://github.com/privatenumber/minification-benchmarks/issues/276#issuecomment-1366261702, the createRequire is from CommonJS. We should switch to ESM only ideally.

  • Minify regex in markdown format failing

    Minify regex in markdown format failing

    Hi, i have a hugo site with documentation. I regularly share in this documentation regular expressions in markdown format (blocks with ```). But I have very often errors in the build because of minify.

    For example :

    userTest ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /var/userTest/[[\:xdigit\:]]*/repos/repo[[\:digit\:]]*, /usr/bin/mkdir -p /var/userTest/[[\:xdigit\:]]*/.ssh
    

    I have this error :

    Error: Error building site: MINIFY: failed to transform "index.js" (application/javascript): unexpected \ in expression on line 158 and column 3552
      158: ...t\\:]]*/repos/repo[[\\:digit\\:]]*, /usr/...
    

    My temporary fix is to disable JS with config.toml :

    [minify]
      disableJS = true
    

    What do you recommend to share regular expressions in markdown format like I do regularly ?

    Thanks

  • JS: simplify static boolean expressions

    JS: simplify static boolean expressions

    Example of code which is not optimized:

    if (true) {
    console.log("true")
    }
    if (false) {
    console.log("false")
    }
    

    It is minified into !0&&console.log("true"),!1&&console.log("false") while it would be better to pre-evaluate the expression and just minify it into console.log("true") (of course only static boolean values).

    Thanks!

  • Build & Release  shared libraries

    Build & Release shared libraries

    Hey,

    I am currently building a .net wrapper for minify. Therefore i would need the shared libraries for all the arches of linux, windows and MacOS.

    All supported versions:

    OS | Binaries -- | -- Linux | Arm32 | Arm32 Alpine | Arm64 | Arm64 Alpine | x64 | x64 Alpine macOS | Arm64 | x64 Windows | Arm64 | x64 | x86 All |  

    Source: https://dotnet.microsoft.com/en-us/download/dotnet/7.0

    A 1:1 copy of the js binding lib would be enougth

    On windows i use go build --buildmode=c-shared -ldflags="-s -w" -o main.dll .\minify.go to build a dll which works for me

  • Python binding hangs in forked process

    Python binding hangs in forked process

    When using the Python bindings with multiprocessing (forking the process via a Pool, to parallelize the minify work), minify appears to hang.

    In particular, this seems to happen when minify is imported via the main process, prior to forking the new processes.

    A workaround is to ensure that the library doesn't get imported prior to forking (i.e., have the new forked processes perform the import).

    I'm not sure if there's any easy library change to fix this, but I at least wanted to let others know of the workaround.

    --

    I'm using Python3.10.4; the installed go version is go1.17.1; Ubuntu 22.04.1.

Build "Dictionary of the Old Norwegian Language" into easier-to-use data formats

Old Norwegian Dictionary Builder Build "Dictionary of the Old Norwegian Language" into easier-to-use data formats. Available formats: JSON DSL XML Usa

Oct 11, 2022
Extract structured data from web sites. Web sites scraping.
Extract structured data from web sites. Web sites scraping.

Dataflow kit Dataflow kit ("DFK") is a Web Scraping framework for Gophers. It extracts data from web pages, following the specified CSS Selectors. You

Jan 7, 2023
Geziyor, a fast web crawling & scraping framework for Go. Supports JS rendering.

Geziyor Geziyor is a blazing fast web crawling and web scraping framework. It can be used to crawl websites and extract structured data from them. Gez

Dec 29, 2022
🌭 The hotdog web browser and browser engine 🌭
🌭 The hotdog web browser and browser engine 🌭

This is the hotdog web browser project. It's a web browser with its own layout and rendering engine, parsers, and UI toolkit! It's made from scratch e

Dec 30, 2022
yview is a lightweight, minimalist and idiomatic template library based on golang html/template for building Go web application.

wview wview is a lightweight, minimalist and idiomatic template library based on golang html/template for building Go web application. Contents Instal

Dec 5, 2021
Build and deploy resilient web applications.

Archived Due to the security concerns surrounding XML, this package is now archived. Go server overview : Template engine. Built in request tracer. we

Dec 15, 2020
:triangular_ruler:gofmtmd formats go source code block in Markdown. detects fenced code & formats code using gofmt.
:triangular_ruler:gofmtmd formats go source code block in Markdown. detects fenced code & formats code using gofmt.

gofmtmd gofmtmd formats go source code block in Markdown. detects fenced code & formats code using gofmt. Installation $ go get github.com/po3rin/gofm

Oct 31, 2022
Formats discord tokens to different formats.
Formats discord tokens to different formats.

token_formatter Formats discord tokens to different formats. Features Format your current tokens to a new format! Every tool uses a different format f

Nov 3, 2022
Simple logger for Go programs. Allows custom formats for messages.
Simple logger for Go programs. Allows custom formats for messages.

go-logger A simple go logger for easy logging in your programs. Allows setting custom format for messages. Preview Install go get github.com/apsdehal/

Dec 17, 2022
Sparse matrix formats for linear algebra supporting scientific and machine learning applications

Sparse matrix formats Implementations of selected sparse matrix formats for linear algebra supporting scientific and machine learning applications. Co

Dec 12, 2022
Easily create & extract archives, and compress & decompress files of various formats

archiver Introducing Archiver 3.1 - a cross-platform, multi-format archive utility and Go library. A powerful and flexible library meets an elegant CL

Jan 7, 2023
Sparse matrix formats for linear algebra supporting scientific and machine learning applications

Sparse matrix formats Implementations of selected sparse matrix formats for linear algebra supporting scientific and machine learning applications. Co

Jan 8, 2023
golang library to read and write various subtitle formats

libgosubs Golang library to read and write subtitles in the following formats Advanced SubStation Alpha v4 SRT TTML v1.0 - This is based on the spec p

Sep 27, 2022
converts text-formats from one to another, it is very useful if you want to re-format a json file to yaml, toml to yaml, csv to yaml, ... etc

re-txt reformates a text file from a structure to another, i.e: convert from json to yaml, toml to json, ... etc Supported Source Formats json yaml hc

Sep 23, 2022
A Go native tabular data extraction package. Currently supports .xls, .xlsx, .csv, .tsv formats.

grate A Go native tabular data extraction package. Currently supports .xls, .xlsx, .csv, .tsv formats. Why? Grate focuses on speed and stability first

Dec 26, 2022
Dumpling is a fast, easy-to-use tool written by Go for dumping data from the database(MySQL, TiDB...) to local/cloud(S3, GCP...) in multifarious formats(SQL, CSV...).

?? Dumpling Dumpling is a tool and a Go library for creating SQL dump from a MySQL-compatible database. It is intended to replace mysqldump and mydump

Nov 9, 2022
Easily create & extract archives, and compress & decompress files of various formats

archiver Introducing Archiver 3.1 - a cross-platform, multi-format archive utility and Go library. A powerful and flexible library meets an elegant CL

Jan 3, 2023
OctoSQL is a query tool that allows you to join, analyse and transform data from multiple databases and file formats using SQL.
OctoSQL is a query tool that allows you to join, analyse and transform data from multiple databases and file formats using SQL.

OctoSQL OctoSQL is a query tool that allows you to join, analyse and transform data from multiple databases, streaming sources and file formats using

Dec 29, 2022
Fast, realtime regex-extraction, and aggregation into common formats such as histograms, numerical summaries, tables, and more!
Fast, realtime regex-extraction, and aggregation into common formats such as histograms, numerical summaries, tables, and more!

rare A file scanner/regex extractor and realtime summarizor. Supports various CLI-based graphing and metric formats (histogram, table, etc). Features

Dec 29, 2022
CLI tool that can execute SQL queries on CSV, LTSV, JSON and TBLN. Can output to various formats.
CLI tool that can execute SQL queries on CSV, LTSV, JSON and TBLN. Can output to various formats.

trdsql CLI tool that can execute SQL queries on CSV, LTSV, JSON and TBLN. It is a tool like q, textql and others. The difference from these tools is t

Jan 1, 2023