jekyll-minify-js 0.1.1 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +130 -0
  3. data/assets/index.js +30131 -0
  4. data/lib/jekyll-minify-js.rb +116 -91
  5. data/lib/version.rb +11 -0
  6. metadata +18 -54
@@ -1,109 +1,134 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jekyll'
4
- require 'fileutils'
5
- require 'shellwords'
6
- begin
7
- require 'terser'
8
- rescue LoadError
9
- # terser gem not available; plugin will fallback to external CLI or copy
10
- end
11
- require 'pathname'
3
+ require "jekyll"
4
+ require "json"
5
+ require "open3"
6
+ require "pathname"
7
+
8
+ require_relative "version"
12
9
 
13
- # module Jekyll
14
10
  module Jekyll
15
- # MinifyJs is a Jekyll `Generator` that minifies JavaScript files found under
16
- # common asset directories. It prefers the `terser` Ruby gem when available
17
- # and falls back to an external `terser` CLI when necessary.
18
- class MinifyJs < Generator
19
- safe true
20
- priority :low
11
+ # Minifies JavaScript assets after Jekyll finishes writing the site.
12
+ #
13
+ # The plugin reads files from a configured entry directory, invokes the
14
+ # bundled Node-based terser wrapper, and writes the compiled assets into the
15
+ # destination site output.
16
+ module MinifyJs
17
+ # Runs JavaScript minification from Jekyll's post-write lifecycle.
18
+ #
19
+ # The generator itself does not perform work during the normal generation
20
+ # phase. Instead, {run} is invoked from a `:post_write` hook so Jekyll does
21
+ # not overwrite the generated minified files.
22
+ class TerserGenerator < Jekyll::Generator
23
+ safe true
24
+ priority :low
21
25
 
22
- def generate(_site)
23
- # Work is done in the :post_write hook to avoid Jekyll overwriting outputs.
24
- end
26
+ # Declares the generator without doing work during the normal build phase.
27
+ #
28
+ # @param _site [Jekyll::Site] the site being generated
29
+ # @return [void]
30
+ def generate(_site)
31
+ # Work is done in the :post_write hook to avoid Jekyll overwriting outputs.
32
+ end
25
33
 
26
- def self.run(site) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
27
- # Support both repository-root layout and app/ layout.
28
- candidates = [
29
- File.join(site.source, 'assets', 'js'),
30
- File.join(site.source, 'app', 'assets', 'js')
31
- ]
32
- src_dir = candidates.find { |p| Dir.exist?(p) }
33
- return unless src_dir
34
+ # Merges the plugin configuration with defaults.
35
+ #
36
+ # @param site [Jekyll::Site] the current site instance
37
+ # @return [Hash] the merged `minify_js` configuration
38
+ def self.minify_js_config(site)
39
+ default_config = {
40
+ "enabled" => true,
41
+ "entry_dir" => "js",
42
+ "output_dir" => "js",
43
+ "terser_opts" => {
44
+ "source_map" => true,
45
+ "compress" => true
46
+ }
47
+ }
48
+ site.config["minify_js"] = default_config.merge(site.config["minify_js"] || {})
49
+ end
34
50
 
35
- # Read configuration from _config.yml under `minify_js:` key
36
- cfg = site.config.fetch('minify_js', {})
37
- return if cfg.key?('enabled') && cfg['enabled'] == false
51
+ # Runs the Node-based terser wrapper for one JavaScript source string.
52
+ #
53
+ # @param code [String] the JavaScript source to minify
54
+ # @param ts_opts [Hash, nil] terser options passed to the wrapper
55
+ # @return [Hash] the parsed JSON response from the wrapper
56
+ # @raise [RuntimeError] if the wrapper process exits unsuccessfully
57
+ def self.run_terser(code, ts_opts)
58
+ script_path = File.expand_path("../assets/index.js", __dir__)
59
+ input = JSON.generate({
60
+ code: code,
61
+ opts: ts_opts
62
+ }.compact)
63
+ stdout, stderr, status = Open3.capture3("node", script_path, stdin_data: input)
64
+ raise "Minify JS failed: #{stderr}" unless status.success?
38
65
 
39
- dest_dir = File.join(site.dest, cfg.fetch('output_dir', 'assets/js'))
40
- FileUtils.mkdir_p(dest_dir)
66
+ JSON.parse(stdout)
67
+ end
41
68
 
42
- terser_available = defined?(Terser)
69
+ # Finds JavaScript files in the configured entry directory.
70
+ #
71
+ # @param site [Jekyll::Site] the current site instance
72
+ # @param entry [String, nil] the source directory relative to `site.source`
73
+ # @return [Array<String>, nil] matching absolute file paths, or `nil` when
74
+ # no entry directory is configured or no files are found
75
+ def self.entry_js_files(site, entry)
76
+ entry_full = File.join(site.source, entry)
77
+ js_files = Dir.glob(File.join(entry_full, "**", "*.js"))
78
+ if Dir.exist?(entry_full) && js_files.empty?
79
+ Jekyll.logger.warn "MinifyJs:",
80
+ "No JavaScript files found in #{entry_full}."
81
+ end
82
+ return if entry.nil? || js_files.empty?
43
83
 
44
- # Build options from config
45
- compress_opt = cfg.fetch('compress', true)
46
- mangle_opt = cfg.fetch('mangle', true)
47
- source_map_enabled = cfg.fetch('source_map', true)
48
- exclude_patterns = cfg.fetch('exclude', ['**/*.min.js'])
84
+ js_files
85
+ end
49
86
 
50
- Dir.glob(File.join(src_dir, '**', '*.js')).each do |src| # rubocop:disable Metrics/BlockLength
51
- rel = Pathname.new(src).relative_path_from(Pathname.new(src_dir)).to_s
52
- out = File.join(dest_dir, rel)
53
- out_dir = File.dirname(out)
54
- FileUtils.mkdir_p(out_dir)
55
- map_name = "#{File.basename(out)}.map"
87
+ # Resolves the output directory for minified assets.
88
+ #
89
+ # @param site [Jekyll::Site] the current site instance
90
+ # @param out [String, nil] the configured output directory
91
+ # @param entry [String] the configured entry directory
92
+ # @return [String] the absolute destination directory inside `site.dest`
93
+ def self.output_dir(site, out, entry)
94
+ out ? File.join(site.dest, out) : File.join(site.dest, entry)
95
+ end
56
96
 
57
- # Skip excluded files
58
- next if exclude_patterns.any? { |pat| File.fnmatch(pat, rel) }
97
+ # Minifies every JavaScript file configured for the site.
98
+ #
99
+ # Files are written into the resolved output directory. If minification of
100
+ # an individual file fails, the original file is copied instead.
101
+ #
102
+ # @param site [Jekyll::Site] the current site instance
103
+ # @return [void]
104
+ def self.run(site) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
105
+ Jekyll.logger.info "Running jekyll-minify-js v#{Jekyll::MinifyJs::VERSION}"
106
+ mini_js_config = minify_js_config(site)
107
+ entry_dir = mini_js_config["entry_dir"]
108
+ nil if (mini_js_config["enable"] && mini_js_config["enable"] == false) || entry_dir.nil?
109
+ out_dir = output_dir(site, mini_js_config["output_dir"], entry_dir)
110
+ ts_opts = mini_js_config["terser_opts"]
111
+ entry_dir_full = File.join(site.source, entry_dir)
112
+ js_files = entry_js_files(site, entry_dir)
59
113
 
60
- if terser_available
61
- Jekyll.logger.info 'Terser:', "minifying #{rel} via terser Ruby gem"
62
- begin
63
- src_content = File.open(src, 'r:BOM|UTF-8', &:read)
64
- options = {}
65
- options[:compress] = compress_opt
66
- options[:mangle] = mangle_opt
67
- if source_map_enabled
68
- options[:source_map] =
69
- { filename: File.basename(src), output_filename: File.basename(out), sources_content: true,
70
- url: map_name }
71
- compiled, map = Terser.compile_with_map(src_content, options)
72
- else
73
- compiled = Terser.compile(src_content, options)
74
- map = nil
75
- end
76
- compiled += "\n//# sourceMappingURL=#{map_name}\n" unless compiled.include?('sourceMappingURL')
77
- File.write(out, compiled)
78
- File.write("#{out}.map", map) if map
79
- rescue StandardError => e
80
- Jekyll.logger.warn 'Terser:', "ruby terser failed for #{rel}: #{e.message}; copying original"
81
- FileUtils.cp(src, out)
82
- end
83
- else
84
- terser_cmd = new.detect_terser_cmd
85
- if terser_cmd
86
- cmd = %(#{terser_cmd} #{Shellwords.escape(src)} --compress --mangle -o #{Shellwords.escape(out)} --source-map "url='#{map_name}',includeSources") # rubocop:disable Layout/LineLength
87
- Jekyll.logger.info 'Terser:', "minifying #{rel} via external terser"
88
- success = system(cmd)
89
- unless success && File.exist?(out)
90
- Jekyll.logger.warn 'Terser:', "minify failed for #{rel}; copying original"
91
- FileUtils.cp(src, out)
92
- end
93
- else
94
- Jekyll.logger.warn 'Terser:', 'no terser available — copying original JS'
95
- FileUtils.cp(src, out)
96
- end
114
+ js_files.each do |src|
115
+ rel = src.sub(/\A#{Regexp.escape(entry_dir_full + File::SEPARATOR)}/, "")
116
+ out = File.join(out_dir, rel)
117
+ src_content = File.read(src)
118
+ result = run_terser(src_content, ts_opts)
119
+ compiled = result["compiled"]
120
+ source_map = result["source_map"]
121
+ File.write(out, compiled)
122
+ File.write("#{out}.map", source_map) if source_map
123
+ rescue StandardError => e
124
+ Jekyll.logger.warn "jekyll-minify-js:", "failed to minify for #{rel}: #{e.message}; copying original"
125
+ FileUtils.cp(src, out)
97
126
  end
98
127
  end
99
128
  end
100
-
101
- def detect_terser_cmd
102
- # Prefer the bundler-installed terser (from the `terser` gem) via `bundle exec terser`.
103
- return 'bundle exec terser' if system('bundle exec terser --version > /dev/null 2>&1')
104
- return 'terser' if system('terser --version > /dev/null 2>&1')
105
-
106
- nil
107
- end
108
129
  end
109
130
  end
131
+
132
+ Jekyll::Hooks.register :site, :post_write do |site|
133
+ Jekyll::MinifyJs::TerserGenerator.run(site)
134
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ # Namespace for the jekyll-minify-js plugin.
5
+ module MinifyJs
6
+ # Current gem version.
7
+ #
8
+ # @return [String]
9
+ VERSION = "0.2.11"
10
+ end
11
+ end
metadata CHANGED
@@ -1,28 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-minify-js
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.11
5
5
  platform: ruby
6
6
  authors:
7
- - Pho Thin Maung
7
+ - phothinmg
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: fileutils
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '1.8'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '1.8'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: jekyll
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -38,58 +24,36 @@ dependencies:
38
24
  - !ruby/object:Gem::Version
39
25
  version: '4.4'
40
26
  - !ruby/object:Gem::Dependency
41
- name: pathname
27
+ name: open3
42
28
  requirement: !ruby/object:Gem::Requirement
43
29
  requirements:
44
- - - "~>"
30
+ - - ">="
45
31
  - !ruby/object:Gem::Version
46
- version: 0.4.0
32
+ version: 0.2.1
47
33
  type: :runtime
48
34
  prerelease: false
49
35
  version_requirements: !ruby/object:Gem::Requirement
50
36
  requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 0.4.0
54
- - !ruby/object:Gem::Dependency
55
- name: shellwords
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: 0.2.2
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: 0.2.2
68
- - !ruby/object:Gem::Dependency
69
- name: terser
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '1.2'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
37
+ - - ">="
80
38
  - !ruby/object:Gem::Version
81
- version: '1.2'
82
- email: phothinmg@disroot.org
39
+ version: 0.2.1
40
+ email:
41
+ - phothinmg@disroot.org
83
42
  executables: []
84
43
  extensions: []
85
44
  extra_rdoc_files: []
86
45
  files:
87
46
  - LICENSE.txt
47
+ - README.md
48
+ - assets/index.js
88
49
  - lib/jekyll-minify-js.rb
89
- homepage: https://rubygems.org/gems/jekyll-minify-js
50
+ - lib/version.rb
51
+ homepage: https://github.com/phothinmg/jekyll-minify-js
90
52
  licenses:
91
53
  - MIT
92
54
  metadata:
55
+ homepage_uri: https://github.com/phothinmg/jekyll-minify-js
56
+ changelog_uri: https://github.com/phothinmg/jekyll-minify-js/blob/main/CHANGELOG.md
93
57
  rubygems_mfa_required: 'true'
94
58
  rdoc_options: []
95
59
  require_paths:
@@ -98,14 +62,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
62
  requirements:
99
63
  - - ">="
100
64
  - !ruby/object:Gem::Version
101
- version: '3.1'
65
+ version: 3.2.0
102
66
  required_rubygems_version: !ruby/object:Gem::Requirement
103
67
  requirements:
104
68
  - - ">="
105
69
  - !ruby/object:Gem::Version
106
70
  version: '0'
107
71
  requirements: []
108
- rubygems_version: 4.0.11
72
+ rubygems_version: 4.0.15
109
73
  specification_version: 4
110
- summary: Jekyll plugin for Minify Js.
74
+ summary: Jekyll plugin for Minify Js
111
75
  test_files: []