Compare commits

...

6 Commits

Author SHA1 Message Date
Thomas Reynolds 439ecb1887 Minor performance tweak 2016-04-28 10:08:43 -07:00
Thomas Reynolds 87e0f240ff continuing 2016-04-28 09:36:05 -07:00
Thomas Reynolds 2804a61c61 Finish conversion 2016-04-28 09:35:37 -07:00
Thomas Reynolds 0d3030f28c More WIP WIP 2016-04-28 09:35:37 -07:00
Thomas Reynolds 029de6613b More WIP 2016-04-28 09:35:37 -07:00
Brad Gessler 11e478fad9 WIP - Rendering pipeline. 2016-04-28 09:35:37 -07:00
47 changed files with 379 additions and 618 deletions

View File

@ -1,8 +1,9 @@
master master
=== ===
# Next # 4.2.0
* Remove Rack support in favor of `resource.filters << proc { |oldbody| newbody }`
* Expose `development?` and `production?` helpers to template context. * Expose `development?` and `production?` helpers to template context.
# 4.1.8 # 4.1.8

View File

@ -188,23 +188,6 @@ Feature: Assets get file hashes appended to them and references to them are upda
When I go to "/partials/" When I go to "/partials/"
Then I should see 'href="../stylesheets/uses_partials-ec347271.css' Then I should see 'href="../stylesheets/uses_partials-ec347271.css'
Scenario: The asset hash should change when a Rack-based filter changes
Given a fixture app "asset-hash-app"
And a file named "config.rb" with:
"""
activate :asset_hash
activate :relative_assets
activate :directory_indexes
require 'lib/middleware.rb'
use ::Middleware
"""
Given the Server is running at "asset-hash-app"
When I go to "/"
Then I should see 'href="stylesheets/site-5ad7def0.css'
When I go to "stylesheets/site-5ad7def0.css"
Then I should see 'background-image: url("../images/100px-5fd6fb90.jpg")'
Then I should see 'Added by Rack filter'
Scenario: Hashed-asset files are not produced for ignored paths Scenario: Hashed-asset files are not produced for ignored paths
Given a fixture app "asset-hash-app" Given a fixture app "asset-hash-app"
And a file named "config.rb" with: And a file named "config.rb" with:

View File

@ -0,0 +1,17 @@
Feature: Generic block based pages
Scenario: Static Ruby Endpoints
Given an empty app
And a file named "config.rb" with:
"""
endpoint "hello.html" do
"world"
end
"""
And a file named "source/index.html.erb" with:
"""
Hi
"""
And the Server is running at "empty_app"
When I go to "/hello.html"
Then I should see "world"

View File

@ -3,8 +3,9 @@ Feature: Extension author could use some hooks
Scenario: When build Scenario: When build
Given a fixture app "extension-api-deprecations-app" Given a fixture app "extension-api-deprecations-app"
When I run `middleman build` When I run `middleman build`
Then the exit status should be 0 And the exit status should be 0
And the output should contain "`set :layout` is deprecated" And the output should contain "`set :layout` is deprecated"
And the output should contain "Project built successfully"
And the file "build/index.html" should contain "In Index" And the file "build/index.html" should contain "In Index"
And the file "build/index.html" should not contain "In Layout" And the file "build/index.html" should not contain "In Layout"

View File

@ -7,7 +7,6 @@ Feature: Extension author could use some hooks
And the output should contain "/// after_configuration ///" And the output should contain "/// after_configuration ///"
And the output should contain "/// ready ///" And the output should contain "/// ready ///"
And the output should contain "/// before_build ///" And the output should contain "/// before_build ///"
And the output should contain "/// before ///"
And the output should contain "/// before_render ///" And the output should contain "/// before_render ///"
And the output should contain "/// after_render ///" And the output should contain "/// after_render ///"
And the output should contain "/// after_build ///" And the output should contain "/// after_build ///"

View File

@ -1,65 +0,0 @@
Feature: Support Rack apps mounted using map
Scenario: Mounted Rack App at /sinatra
Given the Server is running at "sinatra-app"
When I go to "/"
Then I should see "Hello World (Middleman)"
When I go to "/sinatra/"
Then I should see "Hello World (Sinatra)"
Scenario: Built Mounted Rack App at /sinatra
Given a successfully built app at "sinatra-app"
When I cd to "build"
Then the following files should exist:
| index.html |
Then the following files should not exist:
| sinatra/index.html |
| sinatra/index2.html |
Scenario: Static Ruby Endpoints
Given a fixture app "sinatra-app"
And a file named "config.rb" with:
"""
endpoint "hello.html" do
"world"
end
"""
And the Server is running at "sinatra-app"
When I go to "/hello.html"
Then I should see "world"
Scenario: Built Mounted Rack App at /sinatra (including rack endpoints)
Given a fixture app "sinatra-app"
And a file named "config.rb" with:
"""
require "sinatra"
class MySinatra < Sinatra::Base
get "/" do
"Hello World (Sinatra)"
end
get "/derp.html" do
"De doo"
end
end
map "/sinatra" do
run MySinatra
end
endpoint "sinatra/index2.html", path: "/sinatra/"
endpoint "dedoo.html", path: "/sinatra/derp.html"
endpoint "hello.html" do
"world"
end
"""
And a successfully built app at "sinatra-app"
When I cd to "build"
Then the following files should exist:
| index.html |
| sinatra/index2.html |
| dedoo.html |
And the file "sinatra/index2.html" should contain 'Hello World (Sinatra)'
And the file "dedoo.html" should contain 'De doo'

View File

@ -1,14 +0,0 @@
require "sinatra"
class MySinatra < Sinatra::Base
get "/" do
"Hello World (Sinatra)"
end
get "/derp.html" do
"De doo"
end
end
map "/sinatra" do
run MySinatra
end

View File

@ -1,5 +0,0 @@
---
layout: false
---
Hello World (Middleman)

View File

@ -157,12 +157,12 @@ module Middleman
define_setting :layout, :_auto_layout, 'Default layout name' define_setting :layout, :_auto_layout, 'Default layout name'
# Which file extensions have a layout by default. # Which file extensions have a layout by default.
# @return [Array.<String>] # @return [Set.<String>]
define_setting :extensions_with_layout, %w(.htm .html .xhtml .php), 'Which file extensions have a layout by default.' define_setting :extensions_with_layout, %w(.htm .html .xhtml .php), 'Which file extensions have a layout by default.', set: true
# Which file extensions are "assets." # Which file extensions are "assets."
# @return [Array.<String>] # @return [Array.<String>]
define_setting :asset_extensions, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot .ico .map), 'Which file extensions are treated as assets.' define_setting :asset_extensions, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot .ico .map), 'Which file extensions are treated as assets.', set: true
# Default string encoding for templates and output. # Default string encoding for templates and output.
# @return [String] # @return [String]

View File

@ -2,7 +2,6 @@ require 'pathname'
require 'fileutils' require 'fileutils'
require 'tempfile' require 'tempfile'
require 'parallel' require 'parallel'
require 'middleman-core/rack'
require 'middleman-core/callback_manager' require 'middleman-core/callback_manager'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -39,9 +38,6 @@ module Middleman
@cleaning = opts.fetch(:clean) @cleaning = opts.fetch(:clean)
@parallel = opts.fetch(:parallel, true) @parallel = opts.fetch(:parallel, true)
rack_app = ::Middleman::Rack.new(@app).to_app
@rack = ::Rack::MockRequest.new(rack_app)
@callbacks = ::Middleman::CallbackManager.new @callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [:on_build_event]) @callbacks.install_methods!(self, [:on_build_event])
end end
@ -227,15 +223,7 @@ module Middleman
if resource.binary? if resource.binary?
export_file!(output_file, resource.file_descriptor[:full_path]) export_file!(output_file, resource.file_descriptor[:full_path])
else else
response = @rack.get(::URI.escape(resource.request_path)) export_file!(output_file, binary_encode(resource.render))
# If we get a response, save it to a tempfile.
if response.status == 200
export_file!(output_file, binary_encode(response.body))
else
trigger(:error, output_file, response.body)
return false
end
end end
rescue => e rescue => e
trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}") trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}")

View File

@ -1,3 +1,5 @@
require 'set'
module Middleman module Middleman
module Configuration module Configuration
# A class that manages a collection of documented settings. # A class that manages a collection of documented settings.
@ -129,23 +131,37 @@ module Middleman
def initialize(key, default, description, options={}) def initialize(key, default, description, options={})
@value_set = false @value_set = false
@array_wrapped_value = nil
@array_wrapped_default = nil
self.key = key self.key = key
self.default = default self.default = default
self.description = description self.description = description
self.options = options self.options = options
@array_wrapped_default = if self.default && options[:set] && self.default.is_a?(Array)
Set.new(self.default)
end
end end
# The user-supplied value for this setting, overriding the default # The user-supplied value for this setting, overriding the default
def value=(value) def value=(value)
@value = value @value = value
@value_set = true @value_set = true
@array_wrapped_value = if @value && options[:set] && @value.is_a?(Array)
Set.new(@value)
end
end end
# The effective value of the setting, which may be the default # The effective value of the setting, which may be the default
# if the user has not set a value themselves. Note that even if the # if the user has not set a value themselves. Note that even if the
# user sets the value to nil it will override the default. # user sets the value to nil it will override the default.
def value def value
value_set? ? @value : default if value_set?
@array_wrapped_value ? @array_wrapped_value : @value
else
@array_wrapped_default ? @array_wrapped_default : default
end
end end
# Whether or not there has been a value set beyond the default # Whether or not there has been a value set beyond the default

View File

@ -19,12 +19,6 @@ Middleman::Extensions.register :data, auto_activate: :before_sitemap do
Middleman::CoreExtensions::Data Middleman::CoreExtensions::Data
end end
# Rewrite embedded URLs via Rack
Middleman::Extensions.register :inline_url_rewriter, auto_activate: :before_sitemap do
require 'middleman-core/core_extensions/inline_url_rewriter'
Middleman::CoreExtensions::InlineURLRewriter
end
# Catch and show exceptions at the Rack level # Catch and show exceptions at the Rack level
Middleman::Extensions.register :show_exceptions, auto_activate: :before_configuration, modes: [:server] do Middleman::Extensions.register :show_exceptions, auto_activate: :before_configuration, modes: [:server] do
require 'middleman-core/core_extensions/show_exceptions' require 'middleman-core/core_extensions/show_exceptions'

View File

@ -1,135 +0,0 @@
require 'rack'
require 'rack/response'
require 'memoist'
require 'middleman-core/util'
require 'middleman-core/contracts'
module Middleman
module CoreExtensions
class InlineURLRewriter < ::Middleman::Extension
include Contracts
expose_to_application rewrite_inline_urls: :add
REWRITER_DESCRIPTOR = {
id: Symbol,
proc: Or[Proc, Method],
url_extensions: ArrayOf[String],
source_extensions: ArrayOf[String],
ignore: ArrayOf[::Middleman::Util::IGNORE_DESCRIPTOR],
after: Maybe[Symbol]
}.freeze
def initialize(app, options_hash={}, &block)
super
@rewriters = {}
end
Contract REWRITER_DESCRIPTOR => Any
def add(options)
@rewriters[options] = options
end
def after_configuration
return if @rewriters.empty?
rewriters = @rewriters.values.sort do |a, b|
if b[:after] && b[:after] == a[:id]
1
else
0
end
end
app.use Rack, rewriters: rewriters, middleman_app: @app
end
class Rack
extend Memoist
include Contracts
Contract RespondTo[:call], {
middleman_app: IsA['Middleman::Application'],
rewriters: ArrayOf[REWRITER_DESCRIPTOR]
} => Any
def initialize(app, options={})
@rack_app = app
@middleman_app = options.fetch(:middleman_app)
@rewriters = options.fetch(:rewriters)
all_source_exts = @rewriters
.reduce([]) { |sum, rewriter| sum + rewriter[:source_extensions] }
.flatten
.uniq
@source_exts_regex_text = Regexp.union(all_source_exts).to_s
@all_asset_exts = @rewriters
.reduce([]) { |sum, rewriter| sum + rewriter[:url_extensions] }
.flatten
.uniq
end
def call(env)
status, headers, response = @rack_app.call(env)
# Allow configuration or upstream request to skip all rewriting
return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true'
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app)
return [status, headers, response] unless path =~ /(^\/$)|(#{@source_exts_regex_text}$)/
return [status, headers, response] unless body = ::Middleman::Util.extract_response_text(response)
dirpath = ::Pathname.new(File.dirname(path))
rewritten = ::Middleman::Util.instrument 'inline_url_rewriter', path: path do
::Middleman::Util.rewrite_paths(body, path, @all_asset_exts, @middleman_app) do |asset_path|
uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = uri.host.nil?
full_asset_path = if relative_path
dirpath.join(asset_path).to_s
else
asset_path
end
@rewriters.each do |rewriter|
uid = rewriter.fetch(:id)
# Allow upstream request to skip this specific rewriting
next if env["bypass_inline_url_rewriter_#{uid}"] == 'true'
exts = rewriter.fetch(:url_extensions)
next unless exts.include?(::File.extname(asset_path))
source_exts = rewriter.fetch(:source_extensions)
next unless source_exts.include?(::File.extname(path))
ignore = rewriter.fetch(:ignore)
next if ignore.any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) }
rewrite_ignore = Array(rewriter[:rewrite_ignore] || [])
next if rewrite_ignore.any? { |i| ::Middleman::Util.path_match(i, path) }
proc = rewriter.fetch(:proc)
result = proc.call(asset_path, dirpath, path)
asset_path = result if result
end
asset_path
end
end
::Rack::Response.new(
rewritten,
status,
headers
).finish
end
end
end
end
end

View File

@ -60,7 +60,7 @@ module Middleman
# There are also some less common hooks that can be listened to from within an extension's `initialize` method: # There are also some less common hooks that can be listened to from within an extension's `initialize` method:
# #
# * `app.before_render {|body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered. # * `app.before_render {|body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered.
# * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered. It is also common to install a Rack middleware to do this instead. # * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered.
# * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`). # * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`).
# #

View File

@ -1,18 +1,15 @@
require 'middleman-core/util' require 'middleman-core/util'
require 'middleman-core/rack'
class Middleman::Extensions::AssetHash < ::Middleman::Extension class Middleman::Extensions::AssetHash < ::Middleman::Extension
option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for hashable assets.' option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for hashable assets.', set: true
option :exts, nil, 'List of extensions that get asset hashes appended to them.' option :exts, nil, 'List of extensions that get asset hashes appended to them.', set: true
option :ignore, [], 'Regexes of filenames to skip adding asset hashes to' option :ignore, [], 'Regexes of filenames to skip adding asset hashes to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
require 'addressable/uri'
require 'digest/sha1' require 'digest/sha1'
require 'rack/mock'
# Allow specifying regexes to ignore, plus always ignore apple touch icons # Allow specifying regexes to ignore, plus always ignore apple touch icons
@ignore = Array(options.ignore) + [/^apple-touch-icon/] @ignore = Array(options.ignore) + [/^apple-touch-icon/]
@ -20,14 +17,6 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
# Exclude .ico from the default list because browsers expect it # Exclude .ico from the default list because browsers expect it
# to be named "favicon.ico" # to be named "favicon.ico"
@exts = options.exts || (app.config[:asset_extensions] - %w(.ico)) @exts = options.exts || (app.config[:asset_extensions] - %w(.ico))
app.rewrite_inline_urls id: :asset_hash,
url_extensions: @exts.sort.reverse,
source_extensions: options.sources,
ignore: @ignore,
rewrite_ignore: options.rewrite_ignore,
proc: method(:rewrite_url),
after: :asset_host
end end
Contract String, Or[String, Pathname], Any => Maybe[String] Contract String, Or[String, Pathname], Any => Maybe[String]
@ -45,7 +34,6 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
replacement_path = "/#{asset_page.destination_path}" replacement_path = "/#{asset_page.destination_path}"
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
replacement_path replacement_path
end end
@ -53,9 +41,18 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
# @return Array<Middleman::Sitemap::Resource> # @return Array<Middleman::Sitemap::Resource>
Contract ResourceList => ResourceList Contract ResourceList => ResourceList
def manipulate_resource_list(resources) def manipulate_resource_list(resources)
@rack_client ||= begin resources.each do |r|
rack_app = ::Middleman::Rack.new(app).to_app next unless r.destination_path.end_with?('/', *options.sources)
::Rack::MockRequest.new(rack_app) next if Array(options.rewrite_ignore || []).any? do |i|
::Middleman::Util.path_match(i, "/#{r.destination_path}")
end
r.filters << ::Middleman::InlineURLRewriter.new(:asset_hash,
app,
r,
url_extensions: @exts,
ignore: options.ignore,
proc: method(:rewrite_url))
end end
# Process resources in order: binary images and fonts, then SVG, then JS/CSS. # Process resources in order: binary images and fonts, then SVG, then JS/CSS.
@ -81,15 +78,9 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
digest = if resource.binary? digest = if resource.binary?
::Digest::SHA1.file(resource.source_file).hexdigest[0..7] ::Digest::SHA1.file(resource.source_file).hexdigest[0..7]
else else
# Render through the Rack interface so middleware and mounted apps get a shot # Render without asset hash
response = @rack_client.get( body = resource.render { |f| !f.respond_to?(:filter_name) || f.filter_name != :asset_hash }
::URI.escape(resource.destination_path), ::Digest::SHA1.hexdigest(body)[0..7]
'bypass_inline_url_rewriter_asset_hash' => 'true'
)
raise "#{resource.path} should be in the sitemap!" unless response.status == 200
::Digest::SHA1.hexdigest(response.body)[0..7]
end end
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" } resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }

View File

@ -1,5 +1,3 @@
require 'addressable/uri'
class Middleman::Extensions::AssetHost < ::Middleman::Extension class Middleman::Extensions::AssetHost < ::Middleman::Extension
option :host, nil, 'The asset host to use or a Proc to determine asset host', required: true option :host, nil, 'The asset host to use or a Proc to determine asset host', required: true
option :exts, nil, 'List of extensions that get cache busters strings appended to them.' option :exts, nil, 'List of extensions that get cache busters strings appended to them.'
@ -7,15 +5,22 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
option :ignore, [], 'Regexes of filenames to skip adding query strings to' option :ignore, [], 'Regexes of filenames to skip adding query strings to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for host rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for host rewrites'
def initialize(app, options_hash={}, &block) Contract ResourceList => ResourceList
super def manipulate_resource_list(resources)
resources.each do |r|
next unless r.destination_path.end_with?('/', *options.sources)
next if Array(options.rewrite_ignore || []).any? do |i|
::Middleman::Util.path_match(i, "/#{r.destination_path}")
end
app.rewrite_inline_urls id: :asset_host, r.filters << ::Middleman::InlineURLRewriter.new(:asset_host,
url_extensions: options.exts || app.config[:asset_extensions], app,
source_extensions: options.sources, r,
ignore: options.ignore, after_filter: :asset_hash,
rewrite_ignore: options.rewrite_ignore, url_extensions: options.exts || app.config[:asset_extensions],
proc: method(:rewrite_url) ignore: options.ignore,
proc: method(:rewrite_url))
end
end end
Contract String, Or[String, Pathname], Any => String Contract String, Or[String, Pathname], Any => String

View File

@ -1,19 +1,25 @@
# The Cache Buster extension # The Cache Buster extension
class Middleman::Extensions::CacheBuster < ::Middleman::Extension class Middleman::Extensions::CacheBuster < ::Middleman::Extension
option :exts, nil, 'List of extensions that get cache busters strings appended to them.' option :exts, nil, 'List of extensions that get cache busters strings appended to them.', set: true
option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for bustable assets.' option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for bustable assets.', set: true
option :ignore, [], 'Regexes of filenames to skip adding query strings to' option :ignore, [], 'Regexes of filenames to skip adding query strings to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
def initialize(app, options_hash={}, &block) Contract ResourceList => ResourceList
super def manipulate_resource_list(resources)
resources.each do |r|
next unless r.destination_path.end_with?('/', *options.sources)
next if Array(options.rewrite_ignore || []).any? do |i|
::Middleman::Util.path_match(i, "/#{r.destination_path}")
end
app.rewrite_inline_urls id: :cache_buster, r.filters << ::Middleman::InlineURLRewriter.new(:cache_buster,
url_extensions: options.exts || app.config[:asset_extensions], app,
source_extensions: options.sources, r,
ignore: options.ignore, url_extensions: options.exts || app.config[:asset_extensions],
rewrite_ignore: options.rewrite_ignore, ignore: options.ignore,
proc: method(:rewrite_url) proc: method(:rewrite_url))
end
end end
Contract String, Or[String, Pathname], Any => String Contract String, Or[String, Pathname], Any => String

View File

@ -1,3 +1,5 @@
require 'set'
# Directory Indexes extension # Directory Indexes extension
class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
# This should run after most other sitemap manipulators so that it # This should run after most other sitemap manipulators so that it
@ -11,7 +13,7 @@ class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
index_file = app.config[:index_file] index_file = app.config[:index_file]
new_index_path = "/#{index_file}" new_index_path = "/#{index_file}"
extensions = %w(.htm .html .php .xhtml) extensions = Set.new(%w(.htm .html .php .xhtml))
resources.each do |resource| resources.each do |resource|
# Check if it would be pointless to reroute # Check if it would be pointless to reroute

View File

@ -10,7 +10,7 @@
# to .css, .htm, .html, .js, and .xhtml # to .css, .htm, .html, .js, and .xhtml
# #
class Middleman::Extensions::Gzip < ::Middleman::Extension class Middleman::Extensions::Gzip < ::Middleman::Extension
option :exts, %w(.css .htm .html .js .svg .xhtml), 'File extensions to Gzip when building.' option :exts, %w(.css .htm .html .js .svg .xhtml), 'File extensions to Gzip when building.', set: true
option :ignore, [], 'Patterns to avoid gzipping' option :ignore, [], 'Patterns to avoid gzipping'
option :overwrite, false, 'Overwrite original files instead of adding .gz extension.' option :overwrite, false, 'Overwrite original files instead of adding .gz extension.'

View File

@ -1,5 +1,5 @@
require 'memoist'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'rack/mime'
# Minify CSS Extension # Minify CSS Extension
class Middleman::Extensions::MinifyCss < ::Middleman::Extension class Middleman::Extensions::MinifyCss < ::Middleman::Extension
@ -9,17 +9,10 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
require 'sass' require 'sass'
SassCompressor SassCompressor
}, 'Set the CSS compressor to use.' }, 'Set the CSS compressor to use.'
option :content_types, %w(text/css), 'Content types of resources that contain CSS' option :content_types, %w(text/css), 'Content types of resources that contain CSS', set: true
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline CSS' option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline CSS', set: true
def ready INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
# Setup Rack middleware to minify CSS
app.use Rack, compressor: options[:compressor],
ignore: Array(options[:ignore]) + [/\.min\./],
inline: options[:inline],
content_types: options[:content_types],
inline_content_types: options[:inline_content_types]
end
class SassCompressor class SassCompressor
def self.compress(style, options={}) def self.compress(style, options={})
@ -29,97 +22,61 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
end end
end end
# Rack middleware to look for CSS and compress it def initialize(app, options_hash={}, &block)
class Rack super
extend Memoist
include Contracts
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
# Init @ignore = Array(options[:ignore]) + [/\.min\./]
# @param [Class] app @compressor = options[:compressor]
# @param [Hash] options @compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
Contract RespondTo[:call], { @compressor = @compressor.call if @compressor.is_a? Proc
ignore: ArrayOf[PATH_MATCHER],
inline: Bool,
compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]]
} => Any
def initialize(app, options={})
@app = app
@ignore = options.fetch(:ignore)
@inline = options.fetch(:inline)
@compressor = options.fetch(:compressor)
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
@content_types = options[:content_types]
@inline_content_types = options[:inline_content_types]
end
# Rack interface
# @param [Rack::Environmemt] env
# @return [Array]
def call(env)
status, headers, response = @app.call(env)
content_type = headers['Content-Type'].try(:slice, /^[^;]*/)
path = env['PATH_INFO']
minified = if @inline && minifiable_inline?(content_type)
minify_inline(::Middleman::Util.extract_response_text(response))
elsif minifiable?(content_type) && !ignore?(path)
minify(::Middleman::Util.extract_response_text(response))
end
if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
end
[status, headers, response]
end
private
# Whether the path should be ignored
# @param [String] path
# @return [Boolean]
def ignore?(path)
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable?(content_type)
@content_types.include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable_inline?(content_type)
@inline_content_types.include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
# @param [String] content
# @return [String]
def minify(content)
@compressor.compress(content)
end
memoize :minify
# Detect and minify inline content
# @param [String] content
# @return [String]
def minify_inline(content)
content.gsub(INLINE_CSS_REGEX) do
$1 + minify($2) + $3
end
end
memoize :minify_inline
end end
Contract ResourceList => ResourceList
def manipulate_resource_list(resources)
resources.each do |r|
type = r.content_type.try(:slice, /^[^;]*/)
if options[:inline] && minifiable_inline?(type)
r.filters << method(:minify_inline)
elsif minifiable?(type) && !ignore?(r.destination_path)
r.filters << method(:minify)
end
end
end
# Whether the path should be ignored
Contract String => Bool
def ignore?(path)
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
Contract Maybe[String] => Bool
def minifiable?(content_type)
options[:content_types].include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
Contract Maybe[String] => Bool
def minifiable_inline?(content_type)
options[:inline_content_types].include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
Contract String => String
def minify(content)
@compressor.compress(content)
end
memoize :minify
# Detect and minify inline content
Contract String => String
def minify_inline(content)
content.gsub(INLINE_CSS_REGEX) do
$1 + minify($2) + $3
end
end
memoize :minify_inline
end end

View File

@ -9,122 +9,79 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
require 'uglifier' require 'uglifier'
::Uglifier.new ::Uglifier.new
}, 'Set the JS compressor to use.' }, 'Set the JS compressor to use.'
option :content_types, %w(application/javascript), 'Content types of resources that contain JS' option :content_types, %w(application/javascript), 'Content types of resources that contain JS', set: true
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS' option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS', set: true
def ready INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
# Setup Rack middleware to minify JS
app.use Rack, compressor: options[:compressor], def initialize(app, options_hash={}, &block)
ignore: Array(options[:ignore]) + [/\.min\./], super
inline: options[:inline],
content_types: options[:content_types], @ignore = Array(options[:ignore]) + [/\.min\./]
inline_content_types: options[:inline_content_types] @compressor = options[:compressor]
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
end end
# Rack middleware to look for JS and compress it Contract ResourceList => ResourceList
class Rack def manipulate_resource_list(resources)
extend Memoist resources.each do |r|
include Contracts type = r.content_type.try(:slice, /^[^;]*/)
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m if options[:inline] && minifiable_inline?(type)
r.filters << method(:minify_inline)
# Init elsif minifiable?(type) && !ignore?(r.destination_path)
# @param [Class] app r.filters << method(:minify)
# @param [Hash] options
Contract RespondTo[:call], {
ignore: ArrayOf[PATH_MATCHER],
inline: Bool,
compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]]
} => Any
def initialize(app, options={})
@app = app
@ignore = options.fetch(:ignore)
@inline = options.fetch(:inline)
@compressor = options.fetch(:compressor)
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
@content_types = options[:content_types]
@inline_content_types = options[:inline_content_types]
end
# Rack interface
# @param [Rack::Environmemt] env
# @return [Array]
def call(env)
status, headers, response = @app.call(env)
type = headers['Content-Type'].try(:slice, /^[^;]*/)
@path = env['PATH_INFO']
minified = if @inline && minifiable_inline?(type)
minify_inline(::Middleman::Util.extract_response_text(response))
elsif minifiable?(type) && !ignore?(@path)
minify(::Middleman::Util.extract_response_text(response))
end
if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
end
[status, headers, response]
end
private
# Whether the path should be ignored
# @param [String] path
# @return [Boolean]
def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable?(content_type)
@content_types.include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable_inline?(content_type)
@inline_content_types.include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
# @param [String] content
# @return [String]
def minify(content)
@compressor.compress(content)
rescue ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content
end
memoize :minify
# Detect and minify inline content
# @param [String] content
# @return [String]
def minify_inline(content)
content.gsub(INLINE_JS_REGEX) do |match|
first = $1
inline_content = $2
last = $3
# Only compress script tags that contain JavaScript (as opposed to
# something like jQuery templates, identified with a "text/html" type).
if !first.include?('type=') || first.include?('text/javascript')
first + minify(inline_content) + last
else
match
end
end end
end end
memoize :minify_inline
end end
# Whether the path should be ignored
Contract String => Bool
def ignore?(path)
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
Contract Maybe[String] => Bool
def minifiable?(content_type)
options[:content_types].include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
Contract Maybe[String] => Bool
def minifiable_inline?(content_type)
options[:inline_content_types].include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
Contract String => String
def minify(content)
@compressor.compress(content)
rescue ::ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content
end
memoize :minify
# Detect and minify inline content
Contract String => String
def minify_inline(content)
content.gsub(INLINE_JS_REGEX) do |match|
first = $1
inline_content = $2
last = $3
# Only compress script tags that contain JavaScript (as opposed to
# something like jQuery templates, identified with a "text/html" type).
if !first.include?('type=') || first.include?('text/javascript')
first + minify(inline_content) + last
else
match
end
end
end
memoize :minify_inline
end end

View File

@ -1,26 +1,28 @@
require 'addressable/uri'
# Relative Assets extension # Relative Assets extension
class Middleman::Extensions::RelativeAssets < ::Middleman::Extension class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
option :exts, nil, 'List of extensions that get converted to relative paths.' option :exts, nil, 'List of extensions that get converted to relative paths.', set: true
option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.' option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.', set: true
option :ignore, [], 'Regexes of filenames to skip converting to relative paths.' option :ignore, [], 'Regexes of filenames to skip converting to relative paths.'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites.' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites.'
option :helpers_only, false, 'Allow only Ruby helpers to change paths.' option :helpers_only, false, 'Allow only Ruby helpers to change paths.'
def initialize(app, options_hash={}, &block) Contract ResourceList => ResourceList
super def manipulate_resource_list(resources)
return resources if options[:helpers_only]
if options[:helpers_only] resources.each do |r|
return next unless r.destination_path.end_with?('/', *options.sources)
next if Array(options.rewrite_ignore || []).any? do |i|
::Middleman::Util.path_match(i, "/#{r.destination_path}")
end
r.filters << ::Middleman::InlineURLRewriter.new(:relative_assets,
app,
r,
url_extensions: options.exts || app.config[:asset_extensions],
ignore: options.ignore,
proc: method(:rewrite_url))
end end
app.rewrite_inline_urls id: :relative_assets,
url_extensions: options.exts || app.config[:asset_extensions],
source_extensions: options.sources,
ignore: options.ignore,
rewrite_ignore: options.rewrite_ignore,
proc: method(:rewrite_url)
end end
def mark_as_relative(file_path, opts, current_resource) def mark_as_relative(file_path, opts, current_resource)
@ -53,7 +55,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
end end
def asset_path(kind, source, options={}) def asset_path(kind, source, options={})
super(kind, source, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource)) super(kind, source, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource))
end end
end end

View File

@ -0,0 +1,49 @@
require 'middleman-core/util'
require 'middleman-core/contracts'
module Middleman
class InlineURLRewriter
include Contracts
attr_reader :filter_name
attr_reader :after_filter
def initialize(filter_name, app, resource, options={})
@filter_name = filter_name
@app = app
@resource = resource
@options = options
@after_filter = @options.fetch(:after_filter, nil)
end
Contract String => String
def execute_filter(body)
path = "/#{@resource.destination_path}"
dirpath = ::Pathname.new(File.dirname(path))
::Middleman::Util.instrument 'inline_url_rewriter', path: path do
::Middleman::Util.rewrite_paths(body, path, @options.fetch(:url_extensions), @app) do |asset_path|
uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = uri.host.nil?
full_asset_path = if relative_path
dirpath.join(asset_path).to_s
else
asset_path
end
exts = @options.fetch(:url_extensions)
next unless exts.include?(::File.extname(asset_path))
next if @options.fetch(:ignore).any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) }
result = @options.fetch(:proc).call(asset_path, dirpath, path)
asset_path = result if result
asset_path
end
end
end
end
end

View File

@ -93,9 +93,6 @@ module Middleman
request_path = ::Middleman::Util.full_path(request_path, @middleman) request_path = ::Middleman::Util.full_path(request_path, @middleman)
full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount
# Run before callbacks
@middleman.execute_callbacks(:before)
# Get the resource object for this path # Get the resource object for this path
resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20')) resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))

View File

@ -9,14 +9,13 @@ module Middleman
# Expose `endpoint` # Expose `endpoint`
expose_to_config :endpoint expose_to_config :endpoint
EndpointDescriptor = Struct.new(:path, :request_path, :block) do EndpointDescriptor = Struct.new(:path, :block) do
def execute_descriptor(app, resources) def execute_descriptor(app, resources)
r = EndpointResource.new( r = ::Middleman::Sitemap::CallbackResource.new(
app.sitemap, app.sitemap,
path, path,
request_path &block
) )
r.output = block if block
resources + [r] resources + [r]
end end
@ -24,43 +23,10 @@ module Middleman
# Setup a proxy from a path to a target # Setup a proxy from a path to a target
# @param [String] path # @param [String] path
# @param [Hash] opts The :path value gives a request path if it
# differs from the output path # differs from the output path
Contract String, Or[{ path: String }, Proc] => EndpointDescriptor Contract String, Proc => EndpointDescriptor
def endpoint(path, opts={}, &block) def endpoint(path, &block)
if block_given? EndpointDescriptor.new(path, block)
EndpointDescriptor.new(path, path, block)
else
EndpointDescriptor.new(path, opts[:path] || path, nil)
end
end
end
class EndpointResource < ::Middleman::Sitemap::Resource
Contract Maybe[Proc]
attr_accessor :output
def initialize(store, path, request_path)
super(store, path)
@request_path = ::Middleman::Util.normalize_path(request_path)
end
Contract String
attr_reader :request_path
Contract Bool
def template?
true
end
Contract Args[Any] => String
def render(*)
return output.call if output
end
Contract Bool
def ignored?
false
end end
end end
end end

View File

@ -3,6 +3,7 @@ require 'middleman-core/sitemap/extensions/traversal'
require 'middleman-core/file_renderer' require 'middleman-core/file_renderer'
require 'middleman-core/template_renderer' require 'middleman-core/template_renderer'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'middleman-core/inline_url_rewriter'
module Middleman module Middleman
# Sitemap namespace # Sitemap namespace
@ -39,24 +40,26 @@ module Middleman
attr_reader :metadata attr_reader :metadata
attr_accessor :ignored attr_accessor :ignored
attr_accessor :filters
# Initialize resource with parent store and URL # Initialize resource with parent store and URL
# @param [Middleman::Sitemap::Store] store # @param [Middleman::Sitemap::Store] store
# @param [String] path # @param [String] path
# @param [String] source # @param [String] source
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[IsA['Middleman::SourceFile'], String]] => Any Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Any] => Any
def initialize(store, path, source=nil) def initialize(store, path, source_file=nil)
@store = store @store = store
@app = @store.app @app = @store.app
@path = path @path = path
@ignored = false @ignored = false
@filters = []
source = Pathname(source) if source && source.is_a?(String) source_file = Pathname(source_file) if source_file && source_file.is_a?(String)
@file_descriptor = if source && source.is_a?(Pathname) @file_descriptor = if source_file && source_file.is_a?(Pathname)
::Middleman::SourceFile.new(source.relative_path_from(@app.source_dir), source, @app.source_dir, Set.new([:source]), 0) ::Middleman::SourceFile.new(source_file.relative_path_from(@app.source_dir), source_file, @app.source_dir, Set.new([:source]), 0)
else else
source source_file
end end
@destination_path = @path @destination_path = @path
@ -138,8 +141,45 @@ module Middleman
# Render this resource # Render this resource
# @return [String] # @return [String]
Contract Hash, Hash => String # Contract Maybe[Hash], Maybe[Hash], Maybe[Proc] => String
def render(opts={}, locs={}) def render(opts={}, locs={})
body = render_without_filters(opts, locs)
return body if @filters.empty?
sortable_filters = @filters.select { |f| f.respond_to?(:filter_name) }.sort do |a, b|
if b.after_filter == a.filter_name
1
else
-1
end
end.reverse
n = 0
sorted_filters = @filters.sort_by do |m|
n += 1
idx = sortable_filters.index(m)
[idx.nil? ? 0 : idx, n]
end
sorted_filters.reduce(body) do |output, filter|
if block_given? && !yield(filter)
output
elsif filter.respond_to?(:execute_filter)
filter.execute_filter(output)
elsif filter.respond_to?(:call)
filter.call(output)
else
output
end
end
end
# Render this resource without content filters
# @return [String]
Contract Hash, Hash => String
def render_without_filters(opts={}, locs={})
return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template? return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template?
md = metadata md = metadata
@ -151,7 +191,7 @@ module Middleman
opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext) opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext)
renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s) renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s)
renderer.render(locs, opts) renderer.render(locs, opts).to_str
end end
# A path without the directory index - so foo/index.html becomes # A path without the directory index - so foo/index.html becomes
@ -204,9 +244,10 @@ module Middleman
end end
class StringResource < Resource class StringResource < Resource
def initialize(store, path, contents=nil, &block) Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[String, Proc]] => Any
def initialize(store, path, contents)
@request_path = path @request_path = path
@contents = block_given? ? block : contents @contents = contents
super(store, path) super(store, path)
end end
@ -215,7 +256,28 @@ module Middleman
end end
def render(*) def render(*)
@contents.respond_to?(:call) ? @contents.call : @contents @contents
end
def binary?
false
end
end
class CallbackResource < Resource
Contract IsA['Middleman::Sitemap::Store'], String, Proc => Any
def initialize(store, path, &block)
@request_path = path
@contents = block
super(store, path)
end
def template?
true
end
def render(*)
@contents.call
end end
def binary? def binary?

View File

@ -94,7 +94,7 @@ module Middleman
.transpose .transpose
.map(&::Regexp.method(:union)) .map(&::Regexp.method(:union))
match = / /
\A(?:[^\r\n]*coding:[^\r\n]*\r?\n)? \A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
(?<start>#{start_delims})[ ]*\r?\n (?<start>#{start_delims})[ ]*\r?\n
(?<frontmatter>.*?)[ ]*\r?\n? (?<frontmatter>.*?)[ ]*\r?\n?

View File

@ -56,10 +56,8 @@ module Middleman
Contract String => String Contract String => String
def step_through_extensions(path) def step_through_extensions(path)
while ::Middleman::Util.tilt_class(path) while ext = File.extname(path)
ext = ::File.extname(path) break if ext.empty? || !::Middleman::Util.tilt_class(ext)
break if ext.empty?
yield ext if block_given? yield ext if block_given?
# Strip templating extensions as long as Tilt knows them # Strip templating extensions as long as Tilt knows them

View File

@ -25,13 +25,15 @@ module Middleman
def tilt_class(path) def tilt_class(path)
::Tilt[path] ::Tilt[path]
end end
memoize :tilt_class # memoize :tilt_class
# Normalize a path to not include a leading slash # Normalize a path to not include a leading slash
# @param [String] path # @param [String] path
# @return [String] # @return [String]
Contract String => String Contract Any => String
def normalize_path(path) def normalize_path(path)
return path unless path.is_a?(String)
# The tr call works around a bug in Ruby's Unicode handling # The tr call works around a bug in Ruby's Unicode handling
::URI.decode(path).sub(%r{^/}, '').tr('', '') ::URI.decode(path).sub(%r{^/}, '').tr('', '')
end end

View File

@ -6,22 +6,9 @@ module Middleman
module_function module_function
# Extract the text of a Rack response as a string. Contract String, String, SetOf[String], IsA['::Middleman::Application'], Proc => String
# Useful for extensions implemented as Rack middleware.
# @param response The response from #call
# @return [String] The whole response as a string.
Contract RespondTo[:each] => String
def extract_response_text(response)
# The rack spec states all response bodies must respond to each
result = ''
response.each do |part, _|
result << part
end
result
end
Contract String, String, ArrayOf[String], IsA['::Middleman::Application'], Proc => String
def rewrite_paths(body, path, exts, app, &_block) def rewrite_paths(body, path, exts, app, &_block)
exts = exts.sort_by(&:length).reverse
matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\s\'\"\)\(>]+(#{::Regexp.union(exts)}))/ matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\s\'\"\)\(>]+(#{::Regexp.union(exts)}))/
url_fn_prefix = 'url(' url_fn_prefix = 'url('

View File

@ -1,5 +1,5 @@
module Middleman module Middleman
# Current Version # Current Version
# @return [String] # @return [String]
VERSION = '4.1.7'.freeze unless const_defined?(:VERSION) VERSION = '4.2.0'.freeze unless const_defined?(:VERSION)
end end