pakyow-assets 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c34ffdb89232d029de7ea67d0c441153098183d5
4
+ data.tar.gz: 9212199c36c2c08afdb61b0148d8203069b3e6fe
5
+ SHA512:
6
+ metadata.gz: a5cbc1673ea231a0f72bc71a6fb131e36a99b0ca2ec3af0cdbfa4ef93b05bf6b3c4f7dfc5451ce342e9b140fe36bf494ece811690c4f3d3a83e62e6c6d502de1
7
+ data.tar.gz: 126b7a3f9334e2bbe8b1be17722317885192b94b9d0a0484c3fcd015462c40baae9f6edc3255ec3f554c095066d8505adadfdcb4698448af0f69f4e287d41b05
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Bryan Powell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # pakyow-assets
2
+
3
+ Asset handling for [Pakyow](http://pakyow.com). It's intended to be lightweight
4
+ and fast in development while providing powerful fingerprinting and caching
5
+ support for production environments.
6
+
7
+ In development, assets are compiled on demand rather than up-front. They'll only
8
+ be recompiled again when the source changes.
9
+
10
+ When starting up in production, Pakyow will compile, minify, and fingerprint
11
+ every asset file. Fingerprinted filenames are automatically handled.
12
+
13
+ Bundled preprocessors include: js, css, png, gif, jpg, favicon, and sass.
14
+
15
+ # Installation
16
+
17
+ Place inside your Gemfile
18
+
19
+ gem 'pakyow-assets'
20
+
21
+ Run `bundle install` and restart your app server.
22
+
23
+ # Usage
24
+
25
+ Place all assets in a `app/assets` directory. When compiled, assets will mimic
26
+ the directory structure. During development, all assets will be compiled to a
27
+ `.assets` directory by default. Pakyow will serve requests to all assets when
28
+ running in a development environment. The idea in production is for all assets
29
+ to be in a non-minified state, making debugging easy.
30
+
31
+ *Note that all compiled assets should be excluded from version control as they
32
+ will be compiled and fingerprinted when deployed to a production environment.*
33
+
34
+ In production, assets will automatically be minified and fingerprinted. As a
35
+ developer you don't have to worry about caching or fingerprints; Pakyow takes
36
+ care of all of that for you.
37
+
38
+ ## Configuration
39
+
40
+ There are many configuration options available. Take a look at `lib/config.rb`
41
+ for all the available options.
42
+
43
+ ## Custom Preprocessor
44
+
45
+ Pakyow Assets can easily be extended. Take a look at the existing preprocessors
46
+ in `lib/preprocessors` for examples.
47
+
48
+ ## External Asset Store
49
+
50
+ Any number of asset stores can be registered:
51
+
52
+ ```ruby
53
+ Pakyow::Config.assets.stores[:store_name] = 'absolute_path_to_store'
54
+ ```
55
+
56
+ # License
57
+
58
+ pakyow-assets is released under the [MIT License](http://opensource.org/licenses/MIT).
data/lib/assets.rb ADDED
@@ -0,0 +1,166 @@
1
+ require 'fileutils'
2
+
3
+ module Pakyow
4
+ module Assets
5
+ def self.register_path_with_name(path, name)
6
+ stores[name] = {
7
+ path: path,
8
+ assets: assets_at_path(path)
9
+ }
10
+ end
11
+
12
+ def self.assets_at_path(path)
13
+ Dir.glob(File.join(path, '**/[!_]*.[a-z]*')).map { |asset|
14
+ String.normalize_path(asset[path.length..-1])
15
+ }
16
+ end
17
+
18
+ def self.stores
19
+ @stores ||= {}
20
+ end
21
+
22
+ def self.preprocessors
23
+ @preprocessors ||= {}
24
+ end
25
+
26
+ def self.dependents
27
+ @dependents ||= {}
28
+ end
29
+
30
+ def self.compiled_asset_path_for_request_path(path)
31
+ path = String.normalize_path(path)
32
+ ext = File.extname(path)
33
+
34
+ return unless path =~ /\.(.*)$/
35
+ return unless preprocessor?(ext)
36
+
37
+ path_regex = /#{path.gsub(ext, '')}\.(#{alias_exts(ext).map(&:to_s).join('|')})/
38
+
39
+ @stores.each_pair do |name, info|
40
+ if asset = info[:assets].find { |asset| asset =~ path_regex }
41
+ return compile_asset_at_path(asset, info[:path])
42
+ end
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ def self.normalize_ext(ext)
49
+ ext.gsub(/[^a-z]/, '').to_sym
50
+ end
51
+
52
+ def self.alias_exts(ext)
53
+ preprocessors.select { |_, info|
54
+ info[:output_ext] == normalize_ext(ext)
55
+ }.keys
56
+ end
57
+
58
+ def self.output_ext(ext)
59
+ ".#{preprocessors[normalize_ext(ext)][:output_ext]}"
60
+ end
61
+
62
+ def self.preprocessor?(ext)
63
+ preprocessors.values.map { |info| info[:output_ext] }.flatten.include?(normalize_ext(ext))
64
+ end
65
+
66
+ def self.compile_asset_at_path(asset, path)
67
+ absolute_path = File.join(path, asset)
68
+
69
+ asset_dir = File.dirname(asset)
70
+ asset_ext = output_ext(File.extname(asset))
71
+ asset_file = File.basename(asset, '.*')
72
+
73
+ compiled_path = File.join(
74
+ Pakyow::Config.app.root,
75
+ Pakyow::Config.assets.compiled_asset_path,
76
+ asset_dir,
77
+ "#{asset_file + '-' + asset_hash(absolute_path) + asset_ext}"
78
+ )
79
+
80
+ unless File.exists?(compiled_path)
81
+ FileUtils.mkdir_p(File.dirname(compiled_path))
82
+ FileUtils.rm(Dir.glob(File.join(Pakyow::Config.app.root, Pakyow::Config.assets.compiled_asset_path, asset_dir, "#{asset_file}-*#{asset_ext}")))
83
+ File.open(compiled_path, 'wb+') { |fp| fp.write(preprocess(absolute_path)) }
84
+ end
85
+
86
+ compiled_path
87
+ end
88
+
89
+ def self.preprocessor(*exts, output: nil, fingerprint_contents: false, &block)
90
+ exts.each do |ext|
91
+ preprocessors[ext] = {
92
+ block: block,
93
+ output_ext: output || ext,
94
+ fingerprint_contents: fingerprint_contents
95
+ }
96
+ end
97
+ end
98
+
99
+ def self.dependencies(*exts, &block)
100
+ exts.each do |ext|
101
+ dependents[ext] = block
102
+ end
103
+ end
104
+
105
+ def self.preprocess(path)
106
+ preprocessor = preprocessors[normalize_ext(File.extname(path))]
107
+ block = preprocessor[:block]
108
+
109
+ contents = block.nil? ? File.open(path, 'rb').read : block.call(path)
110
+ preprocessor[:fingerprint_contents] ? mixin_fingerprints(contents) : contents
111
+ end
112
+
113
+ def self.precompile
114
+ if File.exists?(Pakyow::Config.assets.compiled_asset_path)
115
+ FileUtils.rm_r(Pakyow::Config.assets.compiled_asset_path)
116
+ end
117
+
118
+ stores.each do |_, info|
119
+ info[:assets].each do |asset|
120
+ compile_asset_at_path(asset, info[:path])
121
+ absolute_path = File.join(info[:path], asset)
122
+
123
+ fingerprint = Digest::MD5.file(absolute_path).hexdigest
124
+
125
+ fingerprinted_asset = File.join(
126
+ File.dirname(asset),
127
+ "#{File.basename(asset, '.*')}-#{fingerprint + output_ext(File.extname(asset))}",
128
+ )
129
+
130
+ replaceable_asset = File.join(
131
+ File.dirname(asset),
132
+ File.basename(asset, '.*') + output_ext(File.extname(asset)),
133
+ )
134
+
135
+ manifest[replaceable_asset] = fingerprinted_asset
136
+ end
137
+ end
138
+ end
139
+
140
+ def self.manifest
141
+ @manifest ||= {}
142
+ end
143
+
144
+ def self.mixin_fingerprints(content)
145
+ return content if content.nil? || content.empty?
146
+
147
+ manifest.each do |asset, fingerprinted_asset|
148
+ content = content.gsub(asset, fingerprinted_asset)
149
+ end
150
+
151
+ content
152
+ end
153
+
154
+ def self.asset_hash(absolute_path)
155
+ Digest::MD5.hexdigest(dependencies_for(absolute_path).concat([absolute_path]).map { |filename|
156
+ Digest::MD5.file(filename).hexdigest
157
+ }.flatten.join)
158
+ end
159
+
160
+ def self.dependencies_for(absolute_path)
161
+ block = dependents[normalize_ext(File.extname(absolute_path))]
162
+ return [] if block.nil?
163
+ block.call(absolute_path)
164
+ end
165
+ end
166
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,37 @@
1
+ Pakyow::Config.register(:assets) { |config|
2
+
3
+ # registered asset stores
4
+ config.opt :stores, lambda {
5
+ @stores ||= {
6
+ default: File.join(Pakyow::Config.app.root, 'app', 'assets')
7
+ }
8
+ }
9
+
10
+ # whether or not pakyow should host assets
11
+ config.opt :compile_on_request
12
+
13
+ # whether pakyow should compile assets on startup
14
+ config.opt :compile_on_startup
15
+
16
+ # where assets should be compiled to
17
+ config.opt :compiled_asset_path
18
+
19
+ # whether or not to cache the assets
20
+ config.opt :cache
21
+
22
+ # whether or not to minify the assets
23
+ config.opt :minify
24
+
25
+ }.env(:development) { |opts|
26
+ opts.cache = false
27
+ opts.compile_on_request = true
28
+ opts.compile_on_startup = false
29
+ opts.compiled_asset_path = '.assets'
30
+ opts.minify = false
31
+ }.env(:production) { |opts|
32
+ opts.cache = true
33
+ opts.compile_on_request = false
34
+ opts.compile_on_startup = true
35
+ opts.compiled_asset_path = 'public'
36
+ opts.minify = true
37
+ }
data/lib/middleware.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Pakyow
2
+ module Assets
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if Pakyow::Config.assets.compile_on_request
10
+ path = Pakyow::Assets.compiled_asset_path_for_request_path(env['PATH_INFO'])
11
+ else
12
+ path = File.join(Pakyow::Config.assets.compiled_asset_path, env['PATH_INFO'])
13
+ end
14
+
15
+ if path =~ /\.(.*)$/ && File.exists?(path)
16
+ catch :halt do
17
+ app = Pakyow.app.dup
18
+ app.context = AppContext.new(Request.new(env), Response.new)
19
+
20
+ headers = {
21
+ 'Content-Type' => Rack::Mime.mime_type(File.extname(path))
22
+ }
23
+
24
+ if Pakyow::Config.assets.cache
25
+ mtime = File.mtime(path)
26
+ headers['Age'] = (Time.now - mtime).to_i
27
+ headers['Cache-Control'] = 'public, max-age=31536000'
28
+ end
29
+
30
+ [200, headers, File.open(path)]
31
+ end
32
+ else
33
+ @app.call(env)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'assets'
2
+ require_relative 'config'
3
+ require_relative 'middleware'
4
+ require_relative 'version'
5
+
6
+ require_relative 'preprocessors/css-preprocessor'
7
+ require_relative 'preprocessors/image-preprocessor'
8
+ require_relative 'preprocessors/javascript-preprocessor'
9
+ require_relative 'preprocessors/sass-preprocessor'
10
+
11
+ Pakyow::App.after :init do
12
+ config.assets.stores.each_pair do |name, path|
13
+ Pakyow::Assets.register_path_with_name(path, name)
14
+ end
15
+
16
+ if config.assets.compile_on_startup
17
+ Pakyow::App.processor :html do |content|
18
+ Pakyow::Assets.mixin_fingerprints(content)
19
+ end
20
+
21
+ Pakyow.logger.debug 'Precompiling assets...'
22
+ Pakyow::Assets.precompile
23
+ Pakyow.logger.debug 'Finished precompiling!'
24
+ end
25
+ end
26
+
27
+ Pakyow::App.middleware do |builder|
28
+ builder.use Pakyow::Assets::Middleware
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'yui/compressor'
2
+
3
+ Pakyow::Assets.preprocessor :css, fingerprint_contents: true do |path|
4
+ content = File.open(path).read
5
+
6
+ if Pakyow::Config.assets.minify
7
+ begin
8
+ YUI::CssCompressor.new.compress(content)
9
+ rescue YUI::Compressor::RuntimeError
10
+ Pakyow.logger.warn "Unable to minify #{path}; using raw content"
11
+ content
12
+ end
13
+ else
14
+ content
15
+ end
16
+ end
@@ -0,0 +1 @@
1
+ Pakyow::Assets.preprocessor :png, :jpg, :gif, :ico
@@ -0,0 +1,16 @@
1
+ require 'yui/compressor'
2
+
3
+ Pakyow::Assets.preprocessor :js, fingerprint_contents: true do |path|
4
+ content = File.open(path).read
5
+
6
+ if Pakyow::Config.assets.minify
7
+ begin
8
+ YUI::JavaScriptCompressor.new(munge: true).compress(content)
9
+ rescue YUI::Compressor::RuntimeError
10
+ Pakyow.logger.warn "Unable to minify #{path}; using raw content"
11
+ content
12
+ end
13
+ else
14
+ content
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'sass'
2
+ require 'yui/compressor'
3
+
4
+ Pakyow::Assets.preprocessor :scss, :sass, output: :css, fingerprint_contents: true do |path|
5
+ content = Sass::Engine.for_file(path, {}).render
6
+
7
+ if Pakyow::Config.assets.minify
8
+ begin
9
+ YUI::CssCompressor.new.compress(content)
10
+ rescue YUI::Compressor::RuntimeError
11
+ Pakyow.logger.warn "Unable to minify #{path}; using raw content"
12
+ content
13
+ end
14
+ else
15
+ content
16
+ end
17
+ end
18
+
19
+ Pakyow::Assets.dependencies :scss, :sass do |path|
20
+ Sass::Engine.for_file(path, {}).dependencies.map { |dependency|
21
+ dependency.options[:filename]
22
+ }
23
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Pakyow
2
+ module Assets
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../lib/version', __FILE__)
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'pakyow-assets'
5
+ spec.summary = 'Pakyow Assets'
6
+ spec.description = 'Asset Handling for Pakyow'
7
+ spec.author = 'Bryan Powell'
8
+ spec.email = 'bryan@metabahn.com'
9
+ spec.homepage = 'http://pakyow.org'
10
+ spec.version = Pakyow::Assets::VERSION
11
+ spec.require_path = 'lib'
12
+ spec.files = `git ls-files`.split("\n")
13
+ spec.license = 'MIT'
14
+
15
+ spec.add_dependency('pakyow-support', '~> 0')
16
+ spec.add_dependency('pakyow-core', '~> 0')
17
+ spec.add_dependency('pakyow-presenter', '~> 0')
18
+
19
+ spec.add_dependency('sass', '~> 3.4')
20
+ spec.add_dependency('yui-compressor', '~> 0.12')
21
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pakyow-assets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bryan Powell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pakyow-support
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pakyow-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pakyow-presenter
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sass
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yui-compressor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.12'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.12'
83
+ description: Asset Handling for Pakyow
84
+ email: bryan@metabahn.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - LICENSE
90
+ - README.md
91
+ - lib/assets.rb
92
+ - lib/config.rb
93
+ - lib/middleware.rb
94
+ - lib/pakyow-assets.rb
95
+ - lib/preprocessors/css-preprocessor.rb
96
+ - lib/preprocessors/image-preprocessor.rb
97
+ - lib/preprocessors/javascript-preprocessor.rb
98
+ - lib/preprocessors/sass-preprocessor.rb
99
+ - lib/version.rb
100
+ - pakyow-assets.gemspec
101
+ homepage: http://pakyow.org
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.5
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Pakyow Assets
125
+ test_files: []
126
+ has_rdoc: