├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── html-minifier └── html-minifier.min.js ├── shard.yml ├── spec ├── html-minifier_spec.cr └── spec_helper.cr └── src └── html-minifier.cr /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | *.dwarf 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in applications that use them 9 | /shard.lock 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | 3 | # Uncomment the following if you'd like Travis to run specs and check code formatting 4 | # script: 5 | # - crystal spec 6 | # - crystal tool format --check 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Sam Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html-minifier 2 | 3 | html-minifier embeds the widely-used [html-minifier](https://www.npmjs.com/package/html-minifier) 4 | NPM package in an easy-to-use Crystal shard via [duktape.cr](https://github.com/svaarala/duktape), 5 | which provides a fast, embedded Javascript execution environment within Crystal. 6 | 7 | html-minifier can be used to minify arbitrary HTML content, including Javascript and/or CSS. 8 | 9 | Some features: 10 | * minifies HTML and any embedded CSS/Javascript within the HTML 11 | * no non-Crystal dependencies (no Node.js or NPM required) 12 | * all html-minifier Javascript is baked into the shard, so you won't need to package any extra files with your app/tool/library 13 | * doesn't embed an entire Node.js runtime (Javascript is executed via duktape.cr) 14 | * simple, Crystal-based API (`HtmlMinifier.minify!("source code")` 15 | * full support for html-minifier [options](https://github.com/kangax/html-minifier#options-quick-reference) via `HtmlMinifier.set_options` 16 | * sane, one-size-fits-all options are included by default (unlike html-minifier on NPM) 17 | 18 | 19 | ## Installation 20 | 21 | 1. Add the dependency to your `shard.yml`: 22 | 23 | ```yaml 24 | dependencies: 25 | html-minifier: 26 | github: sam0x17/html-minifier 27 | ``` 28 | 29 | 2. Run `shards install` 30 | 31 | ## Minification 32 | 33 | ```crystal 34 | require "html-minifier" 35 | 36 | HtmlMinifier.minify!(" minify me!") # => " minify me!" 37 | HtmlMinifier.minify!("") # => "" 38 | HtmlMinifier.minify!("") # => "" 39 | ``` 40 | 41 | ## Configuration 42 | All options supported by html-minifier on NPM are supported by this shard. Options can be specified 43 | by a `JSON::Any` object or by a JSON string, as shown below. 44 | 45 | ```crystal 46 | require "html-minifier" 47 | 48 | HtmlMinifier.minify!("") # => "" 49 | 50 | HtmlMinifier.set_options("{\"removeComments\": false}") 51 | 52 | HtmlMinifier.minify!("") # => "" 53 | ``` 54 | 55 | Note that user-specified options will override their respective default values. The default 56 | values for all options are shown below: 57 | 58 | ```json 59 | { 60 | "caseSensitive": true, 61 | "conservativeCollapse": true, 62 | "minifyCSS": true, 63 | "minifyJS": true, 64 | "useShortDoctype": true, 65 | "removeTagWhitespace": true, 66 | "removeScriptTypeAttributes": true, 67 | "removeComments": true, 68 | "collapseWhitespace": true, 69 | "collapseInlineTagWhitespace": true, 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: html-minifier 2 | version: 0.1.0 3 | 4 | authors: 5 | - Sam Johnson 6 | 7 | crystal: 0.31.0 8 | 9 | license: MIT 10 | 11 | dependencies: 12 | duktape: 13 | github: jessedoyle/duktape.cr 14 | version: ~> 1.0.0 15 | -------------------------------------------------------------------------------- /spec/html-minifier_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe HtmlMinifier do 4 | it "minifies HTML" do 5 | html = " yay omg \n\n\n" 6 | HtmlMinifier.minify!(html).should eq " yay omg " 7 | end 8 | 9 | it "minifies CSS" do 10 | html = "\n" 11 | result = " " 12 | HtmlMinifier.minify!(html).should eq result 13 | end 14 | 15 | it "minifies Javascript" do 16 | html = "" 17 | result = "" 18 | HtmlMinifier.minify!(html).should eq result 19 | end 20 | 21 | it "handles newlines" do 22 | html = <<-HTML 23 | 24 | test 25 | 26 | HTML 27 | HtmlMinifier.minify!(html).should eq " test " 28 | end 29 | 30 | it "handles double quotes" do 31 | html = "
" 32 | result = "
" 33 | HtmlMinifier.minify!(html).should eq result 34 | end 35 | 36 | it "allows user-specified options" do 37 | HtmlMinifier.minify!("").should eq "" 38 | HtmlMinifier.set_options("{\"removeComments\":false}") 39 | HtmlMinifier.minify!("").should eq "" 40 | end 41 | 42 | it "handles usage example 1 correctly" do 43 | HtmlMinifier.minify!(" minify me!").should eq " minify me!" 44 | end 45 | 46 | it "handles usage example 2 correctly" do 47 | HtmlMinifier.minify!("").should eq "" 48 | end 49 | 50 | it "handles usage example 3 correctly" do 51 | HtmlMinifier.minify!("").should eq "" 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/html-minifier" 3 | -------------------------------------------------------------------------------- /src/html-minifier.cr: -------------------------------------------------------------------------------- 1 | require "duktape" 2 | require "json" 3 | 4 | HTML_MINIFIER_JS = {{read_file("#{__DIR__}/../html-minifier/html-minifier.min.js")}} 5 | 6 | DEFAULT_OPTIONS = <<-JS 7 | var options = { 8 | caseSensitive: true, 9 | conservativeCollapse: true, 10 | minifyCSS: true, 11 | minifyJS: true, 12 | useShortDoctype: true, 13 | removeTagWhitespace: true, 14 | removeScriptTypeAttributes: true, 15 | removeComments: true, 16 | collapseWhitespace: true, 17 | collapseInlineTagWhitespace: true, 18 | } 19 | JS 20 | 21 | module HtmlMinifier 22 | def self.initialize_context(options_json : String? = nil) 23 | ctx = Duktape::Sandbox.new 24 | ctx.eval!(HTML_MINIFIER_JS) 25 | ctx.eval!(DEFAULT_OPTIONS) 26 | if options_json 27 | options_json = options_json.not_nil! 28 | options_json = options_json.gsub("\"", "\\\"") 29 | options_json = options_json.gsub("\n", "\\n") 30 | ctx.eval!("var user_options = JSON.parse(\"#{options_json}\")") 31 | ctx.eval!("for(var key in user_options) options[key] = user_options[key];") 32 | end 33 | ctx.eval!("var minify = require('html-minifier').minify;") 34 | ctx 35 | end 36 | @@ctx : Duktape::Sandbox? = nil 37 | 38 | def self.minify!(source : String) 39 | @@ctx ||= initialize_context 40 | ctx = @@ctx.not_nil! 41 | source = source.gsub("\n", "\\n") 42 | source = source.gsub("'", "\\'") 43 | ctx.eval!("var output = minify(\'#{source}\', options);") 44 | ctx.eval!("output") 45 | ctx.get_string(-1).not_nil! 46 | end 47 | 48 | def self.set_options(options : String) 49 | @@ctx = initialize_context(options) 50 | end 51 | 52 | def self.set_options(options : JSON::Any) 53 | @@ctx = initialize_context(options.to_json) 54 | end 55 | end 56 | --------------------------------------------------------------------------------