mjml-rails 4.12.1 → 4.15.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '01907552b34958af3e345325340d7c93d23fff1ab5510b8a0af948b2f58b66f1'
4
- data.tar.gz: 7cf08f76b582345db1a6e445c88bdb347031fa090e4af5aaa155d4fe834b31ef
3
+ metadata.gz: 9c6559e7522a96885f084a543108e00fc80bf778dd371edbf6a67ffb20c8269a
4
+ data.tar.gz: e14f43e0de4ad7524c1774784552299fd9689b1643b9504e73f44bed92677c2e
5
5
  SHA512:
6
- metadata.gz: 562b509f671c502a942e0ba76501441d0cad4670ca73514ae43ad82814f5e9e0bb853a89c8128617f3a7026cd0a7c591b43b999d3cc7bc2aed45b5e975f2ba63
7
- data.tar.gz: e57778aadf44939220cce799f9b80eab9e517c6c3d69fecf00fb8f35ddf7ec1a7ef517060ac63804606a23a93c834b7a9a9ca0635cbc9fd994ea266f74c89590
6
+ metadata.gz: fe6820680ae3e42c303ac19d687b2fd01b7c36eb16f9deb590a34e3fe0eb6fd55774f09f95514a46239d233c2209b73247f0e8c577a2c3902e509596c0f8e7ac
7
+ data.tar.gz: 79ef029fc1a7cecdc35bb3e7681edb865968e5263e110bf7a0b1a87316a6d403e60d6e8383936fda29d6494e118cbf64b8756599933291b6711a63de08fd0548
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -68,6 +68,14 @@ Run the following command to install it:
68
68
  bundle install
69
69
  ```
70
70
 
71
+ After installation, you can generate the MJML initializer file:
72
+
73
+ ```console
74
+ rails generate mjml:install
75
+ ```
76
+
77
+ This will create `config/initializers/mjml.rb` with default configuration options.
78
+
71
79
  Add the MJML parser to your project with your favourite package manager:
72
80
 
73
81
  ```console
@@ -124,7 +132,7 @@ MJML-Rails has the following settings with defaults:
124
132
 
125
133
  ERB can be used inside MJML templates by default. Possible values are all template languages that you have installed, e.g. `:haml` or `:slim`.
126
134
 
127
- **Note:** If youre using Haml/Slim layouts, please dont put `<mjml>` in comments in your partial. Read more at [#34](https://github.com/sighmon/mjml-rails/issues/34).
135
+ **Note:** If you're using Haml/Slim layouts, please don't put `<mjml>` in comments in your partial. Read more at [#34](https://github.com/sighmon/mjml-rails/issues/34).
128
136
 
129
137
  - `raise_render_exception: true`
130
138
 
@@ -155,6 +163,9 @@ MJML-Rails has the following settings with defaults:
155
163
  - `use_mrml: false`
156
164
  Enabling this will allow you to use Rust implementation of MJML via the `mrml` gem. It comes with prebuilt binaries instead of having to install MJML along with Node. When enabled the options `mjml_binary_version_supported`, `mjml_binary`, `minify`, `beautify` and `validation_level` are ignored.
157
165
 
166
+ - `cache_mjml: false`
167
+ By default, MJML-Rails does not cache compiled templates. Setting this to `true` will cache compiled templates in `tmp/mjml_cache` to improve performance for frequently used templates.
168
+
158
169
  - `fonts`
159
170
  By default, MJML-Rails uses MJML default fonts, but enables you to override it.
160
171
  Example : `config.fonts = { Raleway: 'https://fonts.googleapis.com/css?family=Raleway }`
@@ -185,6 +196,9 @@ Mjml.setup do |config|
185
196
 
186
197
  # Use default system fonts instead of google fonts
187
198
  config.fonts = {}
199
+
200
+ # Uncomment this to enable template caching
201
+ # config.cache_mjml = true
188
202
  end
189
203
  ```
190
204
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Mjml
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'Creates MJML initializer for your application'
10
+
11
+ def copy_initializer
12
+ template 'mjml.rb', 'config/initializers/mjml.rb'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # MJML configuration
4
+ Mjml.setup do |config|
5
+ # Use :erb as a template language
6
+ config.template_language = :erb
7
+
8
+ # Raise exceptions for template errors
9
+ config.raise_render_exception = true
10
+
11
+ # Beautify the output HTML
12
+ config.beautify = true
13
+
14
+ # Minify the output HTML
15
+ config.minify = false
16
+
17
+ # Validation level for MJML templates
18
+ # Possible values: 'strict', 'soft'
19
+ config.validation_level = 'strict'
20
+
21
+ # Use MRML instead of MJML (requires mrml gem)
22
+ config.use_mrml = false
23
+
24
+ # Cache compiled templates for better performance
25
+ config.cache_mjml = false
26
+
27
+ # Custom fonts configuration
28
+ # Example: config.fonts = { Raleway: 'https://fonts.googleapis.com/css?family=Raleway' }
29
+ config.fonts = nil
30
+
31
+ # Uncomment this to enable template caching
32
+ # config.cache_mjml = true
33
+ end
data/lib/mjml/cache.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mjml
4
+ class Cache
5
+ attr_reader :template_path
6
+
7
+ def initialize(template_path)
8
+ @template_path = template_path
9
+ end
10
+
11
+ # @yield [] -> String
12
+ # @return [String]
13
+ def cache(&block)
14
+ return yield if !Mjml.cache_mjml && block
15
+
16
+ cached_path = cached_file_path
17
+ if File.exist?(cached_path)
18
+ File.read(cached_path)
19
+ else
20
+ html_content = yield if block
21
+ File.write(cached_path, html_content)
22
+ html_content
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def cached_file_path
29
+ File.join(cache_directory, "#{fingerprint}.html")
30
+ end
31
+
32
+ def fingerprint
33
+ full_path = File.join(Dir.pwd, 'app', 'views', "#{template_path}.mjml")
34
+ raise "Template file not found: #{full_path}" unless File.exist?(full_path)
35
+
36
+ Digest::SHA256.hexdigest(File.read(full_path))
37
+ end
38
+
39
+ def cache_directory
40
+ dir = File.join(Dir.pwd, 'tmp', 'mjml_cache')
41
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
42
+ dir
43
+ end
44
+ end
45
+ end
data/lib/mjml/handler.rb CHANGED
@@ -17,6 +17,7 @@ module Mjml
17
17
  compiled_source = compile_source(source, template)
18
18
 
19
19
  parser_class = Mjml.use_mrml ? 'MrmlParser' : 'Parser'
20
+ template_path = template.virtual_path
20
21
  # Per MJML v4 syntax documentation[0] valid/render'able document MUST start with <mjml> root tag
21
22
  # If we get here and template source doesn't start with one it means
22
23
  # that we are rendering partial named according to legacy naming convention (partials ending with '.mjml')
@@ -25,7 +26,7 @@ module Mjml
25
26
  #
26
27
  # [0] - https://github.com/mjmlio/mjml/blob/master/doc/components_1.md#mjml
27
28
  if /<mjml.*?>/i.match?(compiled_source)
28
- "Mjml::#{parser_class}.new(begin;#{compiled_source};end).render.html_safe"
29
+ "Mjml::#{parser_class}.new('#{template_path}', begin;#{compiled_source};end).render.html_safe"
29
30
  else
30
31
  compiled_source
31
32
  end
@@ -2,27 +2,32 @@
2
2
 
3
3
  module Mjml
4
4
  class MrmlParser
5
- attr_reader :input
5
+ attr_reader :template_path, :input
6
6
 
7
7
  # Create new parser
8
8
  #
9
+ # @param template_path [String] The path to the .mjml file
9
10
  # @param input [String] The string to transform in html
10
- def initialize(input)
11
- @input = input
11
+ def initialize(template_path, input)
12
+ @template_path = template_path
13
+ @input = input
14
+ @with_cache = Cache.new(template_path)
12
15
  end
13
16
 
14
17
  # Render mjml template
15
18
  #
16
19
  # @return [String]
17
20
  def render
18
- MRML.to_html(input)
19
- rescue NameError
20
- Mjml.logger.fatal('MRML is not installed. Please add `gem "mrml"` to your Gemfile.')
21
- raise
22
- rescue StandardError
23
- raise if Mjml.raise_render_exception
21
+ @with_cache.cache do
22
+ MRML.to_html(input)
23
+ rescue NameError
24
+ Mjml.logger.fatal('MRML is not installed. Please add `gem "mrml"` to your Gemfile.')
25
+ raise
26
+ rescue StandardError
27
+ raise if Mjml.raise_render_exception
24
28
 
25
- ''
29
+ ''
30
+ end
26
31
  end
27
32
  end
28
33
  end
data/lib/mjml/parser.rb CHANGED
@@ -1,36 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
4
+ require_relative 'cache'
5
+
3
6
  module Mjml
4
7
  class Parser
5
8
  class ParseError < StandardError; end
6
9
 
7
- attr_reader :input
10
+ attr_reader :template_path, :input
8
11
 
9
12
  # Create new parser
10
13
  #
11
- # @param input [String] The string to transform in html
12
- def initialize(input)
14
+ # @param template_path [String] The path to the .mjml file
15
+ # @param input [String] The content of the .mjml file
16
+ def initialize(template_path, input)
13
17
  raise Mjml.mjml_binary_error_string unless Mjml.valid_mjml_binary
14
18
 
15
- @input = input
19
+ @template_path = template_path
20
+ @input = input
21
+ @with_cache = Cache.new(template_path)
16
22
  end
17
23
 
18
- # Render mjml template
24
+ # rubocop:disable Metrics/MethodLength
25
+ # Render MJML template
19
26
  #
20
27
  # @return [String]
21
28
  def render
22
- in_tmp_file = Tempfile.open(['in', '.mjml']) do |file|
23
- file.write(input)
24
- file # return tempfile from block so #unlink works later
25
- end
26
- run(in_tmp_file.path)
27
- rescue StandardError
28
- raise if Mjml.raise_render_exception
29
+ @with_cache.cache do
30
+ in_tmp_file = Tempfile.open(['in', '.mjml']) do |file|
31
+ file.write(input)
32
+ file # return tempfile from block so #unlink works later
33
+ end
34
+ run(in_tmp_file.path)
35
+ rescue StandardError
36
+ raise if Mjml.raise_render_exception
29
37
 
30
- ''
31
- ensure
32
- in_tmp_file&.unlink
38
+ ''
39
+ ensure
40
+ in_tmp_file&.unlink
41
+ end
33
42
  end
43
+ # rubocop:enable Metrics/MethodLength
34
44
 
35
45
  # Exec mjml command
36
46
  #
data/lib/mjml/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mjml
4
4
  # Version number no longer matches MJML.io version
5
- VERSION = '4.12.1'
5
+ VERSION = '4.15.1'
6
6
  end
data/lib/mjml.rb CHANGED
@@ -19,7 +19,8 @@ module Mjml
19
19
  :raise_render_exception,
20
20
  :template_language,
21
21
  :validation_level,
22
- :use_mrml
22
+ :use_mrml,
23
+ :cache_mjml
23
24
 
24
25
  mattr_writer :valid_mjml_binary
25
26
 
@@ -33,6 +34,7 @@ module Mjml
33
34
  self.validation_level = 'strict'
34
35
  self.use_mrml = false
35
36
  self.fonts = nil
37
+ self.cache_mjml = false
36
38
 
37
39
  def self.check_version(bin)
38
40
  stdout, _, status = run_mjml('--version', mjml_bin: bin)
@@ -45,12 +47,24 @@ module Mjml
45
47
  Open3.capture3("#{mjml_bin} #{args}")
46
48
  end
47
49
 
50
+ def self.which(command)
51
+ if Gem.win_platform?
52
+ stdout, _, status = Open3.capture3("where #{command}")
53
+ else
54
+ stdout, _, status = Open3.capture3("which #{command}")
55
+ end
56
+ status.success? ? stdout.chomp : nil
57
+ rescue Errno::ENOENT
58
+ nil
59
+ end
60
+
48
61
  def self.valid_mjml_binary
49
62
  self.valid_mjml_binary = @@valid_mjml_binary ||
50
63
  check_for_custom_mjml_binary ||
64
+ check_for_mrml_binary ||
65
+ check_for_bun_mjml_binary ||
51
66
  check_for_package_mjml_binary ||
52
- check_for_global_mjml_binary ||
53
- check_for_mrml_binary
67
+ check_for_global_mjml_binary
54
68
 
55
69
  return @@valid_mjml_binary if @@valid_mjml_binary
56
70
 
@@ -74,10 +88,8 @@ module Mjml
74
88
  end
75
89
 
76
90
  def self.check_for_mjml_package(package_manager, bin_command, binary_path)
77
- stdout, _, status = Open3.capture3("which #{package_manager}")
78
- return unless status.success?
79
-
80
- pm_bin = stdout.chomp
91
+ pm_bin = Mjml.which(package_manager)
92
+ return unless pm_bin
81
93
 
82
94
  stdout, _, status = Open3.capture3("#{pm_bin} #{bin_command}")
83
95
  return unless status.success?
@@ -88,17 +100,24 @@ module Mjml
88
100
  nil
89
101
  end
90
102
 
103
+ def self.check_for_bun_mjml_binary
104
+ return unless Mjml.which('bun')
105
+
106
+ # HINT: Bun always prioritizes local bins first and falls back to global installations
107
+ mjml_bin = 'bun run mjml'
108
+
109
+ return mjml_bin if check_version(mjml_bin)
110
+ end
111
+
91
112
  def self.check_for_package_mjml_binary
92
- check_for_mjml_package('bun', 'pm bin', ['mjml']) ||
93
- check_for_mjml_package('npm', 'root', ['.bin', 'mjml']) ||
113
+ check_for_mjml_package('npm', 'root', ['.bin', 'mjml']) ||
94
114
  check_for_mjml_package('yarn', 'bin mjml', [])
95
115
  end
96
116
 
97
117
  def self.check_for_global_mjml_binary
98
- stdout, _, status = Open3.capture3('which mjml')
99
- return unless status.success?
118
+ mjml_bin = Mjml.which('mjml')
119
+ return unless mjml_bin
100
120
 
101
- mjml_bin = stdout.chomp
102
121
  return mjml_bin if mjml_bin.present? && check_version(mjml_bin)
103
122
  end
104
123
 
data/test/mjml_test.rb CHANGED
@@ -162,6 +162,7 @@ describe Mjml do
162
162
  it 'can use MRML and check for a valid binary' do
163
163
  Mjml.use_mrml = true
164
164
  Mjml.stubs(:check_for_custom_mjml_binary).returns(false)
165
+ Mjml.stubs(:check_for_bun_mjml_binary).returns(false)
165
166
  Mjml.stubs(:check_for_package_mjml_binary).returns(false)
166
167
  Mjml.stubs(:check_for_global_mjml_binary).returns(false)
167
168
  expect(Mjml.valid_mjml_binary).must_equal(true)
@@ -3,7 +3,7 @@
3
3
  require 'test_helper'
4
4
 
5
5
  describe Mjml::MrmlParser do
6
- let(:parser) { Mjml::MrmlParser.new(input) }
6
+ let(:parser) { Mjml::MrmlParser.new('test_template', input) }
7
7
 
8
8
  describe '#render' do
9
9
  describe 'when input is valid' do
data/test/parser_test.rb CHANGED
@@ -4,7 +4,7 @@ require 'test_helper'
4
4
 
5
5
  describe Mjml::Parser do
6
6
  let(:input) { mock('input') }
7
- let(:parser) { Mjml::Parser.new(input) }
7
+ let(:parser) { Mjml::Parser.new('test_template', input) }
8
8
 
9
9
  describe '#render' do
10
10
  describe 'when exception is raised' do
@@ -42,7 +42,7 @@ describe Mjml::Parser do
42
42
  expect(Mjml.beautify).must_equal(true)
43
43
  expect(Mjml.minify).must_equal(false)
44
44
  expect(Mjml.validation_level).must_equal('strict')
45
- expect(Mjml.fonts).must_equal(nil)
45
+ assert_nil(Mjml.fonts)
46
46
  end
47
47
 
48
48
  it 'uses setup config' do
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,12 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mjml-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.1
4
+ version: 4.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Loffler
8
8
  - Steven Pickles
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain:
12
11
  - |
@@ -36,7 +35,7 @@ cert_chain:
36
35
  Qqk2dn/fFE9vdcl9OaOw2Zizne/1VFL/jkzXLqnLgbKD/q129mVCBgw2CKYnQfMN
37
36
  4RwBjPyqnMxWnSq6Ycn7HdFEkgyf2cAxFfH5QtDsjEuca+/LAJMeAQ==
38
37
  -----END CERTIFICATE-----
39
- date: 2024-09-06 00:00:00.000000000 Z
38
+ date: 2025-04-07 00:00:00.000000000 Z
40
39
  dependencies:
41
40
  - !ruby/object:Gem::Dependency
42
41
  name: byebug
@@ -158,11 +157,14 @@ extra_rdoc_files: []
158
157
  files:
159
158
  - MIT-LICENSE
160
159
  - README.md
160
+ - lib/generators/mjml/install/install_generator.rb
161
+ - lib/generators/mjml/install/templates/mjml.rb
161
162
  - lib/generators/mjml/mailer/mailer_generator.rb
162
163
  - lib/generators/mjml/mailer/templates/layout.html.mjml
163
164
  - lib/generators/mjml/mailer/templates/view.html.erb
164
165
  - lib/mjml-rails.rb
165
166
  - lib/mjml.rb
167
+ - lib/mjml/cache.rb
166
168
  - lib/mjml/handler.rb
167
169
  - lib/mjml/mrml_parser.rb
168
170
  - lib/mjml/parser.rb
@@ -192,8 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
194
  - !ruby/object:Gem::Version
193
195
  version: '0'
194
196
  requirements: []
195
- rubygems_version: 3.5.11
196
- signing_key:
197
+ rubygems_version: 3.6.2
197
198
  specification_version: 4
198
199
  summary: MJML + ERb templates
199
200
  test_files:
metadata.gz.sig CHANGED
Binary file