pakyow-assets 0.1.2 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +3 -19
- data/lib/pakyow/assets.rb +19 -0
- data/lib/pakyow/assets/actions/process.rb +63 -0
- data/lib/pakyow/assets/actions/public.rb +75 -0
- data/lib/pakyow/assets/asset.rb +238 -0
- data/lib/pakyow/assets/babel.rb +34 -0
- data/lib/pakyow/assets/behavior/assets.rb +45 -0
- data/lib/pakyow/assets/behavior/config.rb +137 -0
- data/lib/pakyow/assets/behavior/externals.rb +58 -0
- data/lib/pakyow/assets/behavior/packs.rb +162 -0
- data/lib/pakyow/assets/behavior/prelaunching.rb +19 -0
- data/lib/pakyow/assets/behavior/processing.rb +27 -0
- data/lib/pakyow/assets/behavior/rendering/install_assets.rb +39 -0
- data/lib/pakyow/assets/behavior/silencing.rb +35 -0
- data/lib/pakyow/assets/behavior/watching.rb +25 -0
- data/lib/pakyow/assets/errors.rb +16 -0
- data/lib/pakyow/assets/external.rb +103 -0
- data/lib/pakyow/assets/framework.rb +52 -0
- data/lib/pakyow/assets/pack.rb +171 -0
- data/lib/pakyow/assets/precompiler.rb +63 -0
- data/lib/pakyow/assets/source_map.rb +99 -0
- data/lib/pakyow/assets/tasks/precompile.rake +9 -0
- data/lib/pakyow/assets/tasks/update.rake +20 -0
- data/lib/pakyow/assets/types/css.rb +45 -0
- data/lib/pakyow/assets/types/js.rb +98 -0
- data/lib/pakyow/assets/types/sass.rb +69 -0
- data/lib/pakyow/assets/types/scss.rb +14 -0
- data/src/@babel/standalone@7.3.1/babel.min.js +165 -0
- data/src/@babel/standalone@7.4.5/babel.min.js +92359 -0
- metadata +146 -46
- data/CHANGELOG.md +0 -18
- data/README.md +0 -58
- data/lib/assets.rb +0 -221
- data/lib/config.rb +0 -37
- data/lib/middleware.rb +0 -42
- data/lib/pakyow-assets.rb +0 -31
- data/lib/preprocessors/css-preprocessor.rb +0 -16
- data/lib/preprocessors/image-preprocessor.rb +0 -1
- data/lib/preprocessors/javascript-preprocessor.rb +0 -16
- data/lib/preprocessors/sass-preprocessor.rb +0 -23
- data/lib/version.rb +0 -5
- data/pakyow-assets.gemspec +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bfdfa0ac5ee2f817f474ac114dc81491ff8dbaa4bbbc7a4a15b2020c5c102bd6
|
4
|
+
data.tar.gz: 57af0c11222e8c0ed16e6a3c18627f6d9971d395919cf199b5f13d4b348b0817
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 293fbdf6b36ad59ff7c046374c899c9ba26aab45ff365175e90dc3f2ce8ddd385ae0d8c47909721efa16ddad9d66e9e5fae09884737258bfb167a3dd57ee2124
|
7
|
+
data.tar.gz: 443a19539eff33d4650568901ee1244453b2aa2e36b996202572846dd5fad51980fb567494f421b04de9405833a50a93ece963a18a3f3a842a8656395f256e82
|
data/LICENSE
CHANGED
@@ -1,20 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) Metabahn, LLC
|
2
2
|
|
3
|
-
|
4
|
-
|
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.
|
3
|
+
Pakyow Assets is an open-source project licensed under the terms of the LGPLv3 license.
|
4
|
+
See <https://choosealicense.com/licenses/lgpl-3.0/> for license text.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/routing"
|
4
|
+
require "pakyow/support"
|
5
|
+
require "pakyow/presenter"
|
6
|
+
|
7
|
+
require "pakyow/assets/framework"
|
8
|
+
|
9
|
+
require "pakyow/assets/actions/process"
|
10
|
+
require "pakyow/assets/actions/public"
|
11
|
+
|
12
|
+
require "pakyow/assets/types/js"
|
13
|
+
require "pakyow/assets/types/css"
|
14
|
+
require "pakyow/assets/types/sass"
|
15
|
+
require "pakyow/assets/types/scss"
|
16
|
+
|
17
|
+
module Pakyow
|
18
|
+
config.tasks.paths << File.expand_path("../assets/tasks", __FILE__)
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Assets
|
5
|
+
module Actions
|
6
|
+
# Pipeline Action that processes assets at request time.
|
7
|
+
#
|
8
|
+
# This is intended for development use, please don't use it in production.
|
9
|
+
# Instead, precompile assets into the public directory.
|
10
|
+
#
|
11
|
+
class Process
|
12
|
+
def call(connection)
|
13
|
+
if connection.app.config.assets.process
|
14
|
+
# TODO: can we short circuit if the request path doesn't match the connection?
|
15
|
+
if asset = find_asset(connection) || find_pack(connection) || find_asset_map(connection) || find_pack_map(connection)
|
16
|
+
connection.set_header("content-type", asset.mime_type)
|
17
|
+
connection.body = asset
|
18
|
+
connection.halt
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_asset(connection, path = connection.path)
|
26
|
+
connection.app.state(:asset).find { |asset|
|
27
|
+
asset.public_path == path
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_pack(connection, path = connection.path)
|
32
|
+
connection.app.state(:pack).lazy.map { |pack|
|
33
|
+
pack.packed(path)
|
34
|
+
}.find { |packed| !packed.nil? && packed.any? }
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_asset_map(connection)
|
38
|
+
if wants_map?(connection)
|
39
|
+
if (asset = find_asset(connection, unmapped(connection))) && asset.source_map?
|
40
|
+
asset.source_map
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_pack_map(connection)
|
46
|
+
if (pack = find_pack(connection, unmapped(connection))) && pack.source_map?
|
47
|
+
pack.source_map
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def wants_map?(connection)
|
54
|
+
connection.path.end_with?(".map")
|
55
|
+
end
|
56
|
+
|
57
|
+
def unmapped(connection)
|
58
|
+
File.join(File.dirname(connection.path), File.basename(connection.path, ".map"))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mini_mime"
|
4
|
+
|
5
|
+
require "pakyow/support/core_refinements/string/normalization"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Assets
|
9
|
+
module Actions
|
10
|
+
class Public
|
11
|
+
using Support::Refinements::String::Normalization
|
12
|
+
|
13
|
+
# Pipeline Action that serves files out of your public directory.
|
14
|
+
#
|
15
|
+
def initialize(app)
|
16
|
+
@asset_paths = app.state(:asset).map(&:public_path) + app.state(:pack).flat_map { |pack|
|
17
|
+
[pack.public_css_path, pack.public_js_path]
|
18
|
+
}
|
19
|
+
|
20
|
+
@prefix = if app.is_a?(Plugin)
|
21
|
+
Pathname.new(app.class.mount_path)
|
22
|
+
else
|
23
|
+
Pathname.new("/")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(connection)
|
28
|
+
if connection.app.config.assets.public
|
29
|
+
public_path = public_path(connection)
|
30
|
+
|
31
|
+
if public?(public_path)
|
32
|
+
if mime = MiniMime.lookup_by_filename(public_path)
|
33
|
+
connection.set_header("content-type", mime.content_type)
|
34
|
+
end
|
35
|
+
|
36
|
+
if connection.app.config.assets.cache && asset?(connection)
|
37
|
+
set_cache_headers(connection, public_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
connection.body = File.open(public_path)
|
41
|
+
connection.halt
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def public?(path)
|
49
|
+
File.file?(path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def public_path(connection)
|
53
|
+
File.join(
|
54
|
+
connection.app.config.assets.public_path,
|
55
|
+
String.normalize_path(
|
56
|
+
Pathname.new(connection.path).relative_path_from(@prefix).to_s
|
57
|
+
)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def asset?(connection)
|
62
|
+
@asset_paths.include?(connection.path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_cache_headers(connection, public_path)
|
66
|
+
mtime = File.mtime(public_path)
|
67
|
+
connection.set_header("age", (Time.now - mtime).to_i.to_s)
|
68
|
+
connection.set_header("cache-control", "public, max-age=31536000")
|
69
|
+
connection.set_header("vary", "accept-encoding")
|
70
|
+
connection.set_header("last-modified", mtime.httpdate)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
|
5
|
+
require "pakyow/support/class_state"
|
6
|
+
require "pakyow/support/core_refinements/string/normalization"
|
7
|
+
|
8
|
+
require "pakyow/assets/source_map"
|
9
|
+
|
10
|
+
module Pakyow
|
11
|
+
module Assets
|
12
|
+
# Represents an asset.
|
13
|
+
#
|
14
|
+
# Instances are created when booting in all environments, meaning the app
|
15
|
+
# is guaranteed access to these objects. Contents are loaded and processed
|
16
|
+
# eagerly. This is expected to happen under two scenarios:
|
17
|
+
#
|
18
|
+
# 1. In development, an asset is loaded and processed when it's requested.
|
19
|
+
# 2. In production, when assets are precompiled during a deployment.
|
20
|
+
#
|
21
|
+
class Asset
|
22
|
+
using Support::Refinements::String::Normalization
|
23
|
+
|
24
|
+
extend Support::ClassState
|
25
|
+
class_state :__types, default: []
|
26
|
+
class_state :__emits, default: nil
|
27
|
+
class_state :__extensions, default: [], inheritable: true
|
28
|
+
class_state :__minifiable, default: false, inheritable: true
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def new_from_path(path, config:, source_location: "", prefix: "/", related: [])
|
32
|
+
asset_class = @__types.find { |type|
|
33
|
+
type.__extensions.include?(File.extname(path))
|
34
|
+
} || self
|
35
|
+
|
36
|
+
asset_class.load; asset_class.new(
|
37
|
+
local_path: path,
|
38
|
+
source_location: source_location,
|
39
|
+
config: config,
|
40
|
+
prefix: prefix,
|
41
|
+
related: related
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Implemented by subclasses to load any libraries they need.
|
46
|
+
#
|
47
|
+
def load
|
48
|
+
# intentionally empty
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def inherited(asset_class)
|
53
|
+
@__types << asset_class
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
def update_path_for_emitted_type(path)
|
59
|
+
if @__emits
|
60
|
+
path.sub(File.extname(path), @__emits)
|
61
|
+
else
|
62
|
+
path
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Registers +extension+ for this asset.
|
69
|
+
#
|
70
|
+
def extension(extension)
|
71
|
+
extensions(extension)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Registers multiple extensions for this asset.
|
75
|
+
#
|
76
|
+
def extensions(*extensions)
|
77
|
+
extensions.each do |extension|
|
78
|
+
@__extensions << ".#{extension}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Defines the emitted asset type (e.g. +sass+ emits +css+).
|
83
|
+
#
|
84
|
+
def emits(type)
|
85
|
+
@__emits = ".#{type}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :logical_path, :mime_type, :mime_suffix, :dependencies
|
90
|
+
|
91
|
+
def initialize(local_path:, config:, dependencies: [], source_location: "", prefix: "/", related: [])
|
92
|
+
@local_path, @config, @source_location, @dependencies, @related = local_path, config, source_location, dependencies, related
|
93
|
+
|
94
|
+
@logical_path = self.class.update_path_for_emitted_type(
|
95
|
+
String.normalize_path(
|
96
|
+
File.join(prefix, local_path.sub(source_location, ""))
|
97
|
+
)
|
98
|
+
)
|
99
|
+
|
100
|
+
@public_path = String.normalize_path(
|
101
|
+
File.join(config.prefix, @logical_path)
|
102
|
+
)
|
103
|
+
|
104
|
+
@mime_type = case File.extname(@public_path)
|
105
|
+
when ".js"
|
106
|
+
# Resolves an issue with mini_mime returning `application/ecmascript`
|
107
|
+
#
|
108
|
+
"application/javascript"
|
109
|
+
else
|
110
|
+
MiniMime.lookup_by_filename(@public_path)&.content_type.to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
@mime_prefix, @mime_suffix = @mime_type.split("/", 2)
|
114
|
+
|
115
|
+
@source_map_enabled = config.source_maps
|
116
|
+
|
117
|
+
@mutex = Mutex.new
|
118
|
+
end
|
119
|
+
|
120
|
+
# Overriding and freezing after content is set lets us eagerly process the
|
121
|
+
# content rather than incurring that cost on boot.
|
122
|
+
#
|
123
|
+
def freeze
|
124
|
+
super if instance_variable_defined?(:@content)
|
125
|
+
end
|
126
|
+
|
127
|
+
def each(&block)
|
128
|
+
ensure_content do |content|
|
129
|
+
StringIO.new(post_process(content)).each(&block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def read
|
134
|
+
String.new.tap do |asset|
|
135
|
+
each do |content|
|
136
|
+
asset << content
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def bytesize
|
142
|
+
ensure_content(&:bytesize)
|
143
|
+
end
|
144
|
+
|
145
|
+
def fingerprint
|
146
|
+
[@local_path].concat(dependencies).each_with_object(Digest::MD5.new) { |path, digest|
|
147
|
+
digest.update(Digest::MD5.file(path).hexdigest)
|
148
|
+
}.hexdigest
|
149
|
+
end
|
150
|
+
|
151
|
+
def fingerprinted_filename
|
152
|
+
extension = File.extname(@public_path)
|
153
|
+
File.basename(@public_path, extension) + "__" + fingerprint + extension
|
154
|
+
end
|
155
|
+
|
156
|
+
def public_path
|
157
|
+
if @config.fingerprint
|
158
|
+
File.join(
|
159
|
+
File.dirname(@public_path),
|
160
|
+
fingerprinted_filename
|
161
|
+
)
|
162
|
+
else
|
163
|
+
@public_path
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def source_map?
|
168
|
+
respond_to?(:source_map_content)
|
169
|
+
end
|
170
|
+
|
171
|
+
def source_map
|
172
|
+
if source_map?
|
173
|
+
SourceMap.new(
|
174
|
+
source_map_content,
|
175
|
+
file: File.basename(public_path)
|
176
|
+
)
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def disable_source_map
|
183
|
+
tap do
|
184
|
+
@source_map_enabled = false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def ensure_content
|
191
|
+
@mutex.synchronize do
|
192
|
+
unless frozen? || instance_variable_defined?(:@content)
|
193
|
+
@content = load_content; freeze
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
yield @content if block_given?
|
198
|
+
end
|
199
|
+
|
200
|
+
def load_content
|
201
|
+
content = process(File.read(@local_path)).to_s
|
202
|
+
|
203
|
+
if mime_suffix == "css" || mime_suffix == "javascript"
|
204
|
+
# Update references to related assets with prefixed path, fingerprints.
|
205
|
+
# Do this here rather than in post-processing so that the source maps reflect the changes.
|
206
|
+
#
|
207
|
+
@related.each do |asset|
|
208
|
+
if asset != self && content.include?(asset.logical_path)
|
209
|
+
content.gsub!(asset.logical_path, asset.public_path)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
content
|
215
|
+
end
|
216
|
+
|
217
|
+
def process(content)
|
218
|
+
content
|
219
|
+
end
|
220
|
+
|
221
|
+
def post_process(content)
|
222
|
+
if @source_map_enabled && source_map?
|
223
|
+
embed_mapping_url(content)
|
224
|
+
else
|
225
|
+
content
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def embed_mapping_url(content)
|
230
|
+
content + SourceMap.mapping_url(path: public_path, type: @mime_suffix)
|
231
|
+
end
|
232
|
+
|
233
|
+
def external?
|
234
|
+
File.dirname(@local_path) == @config.externals.path
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mini_racer"
|
4
|
+
require "execjs"
|
5
|
+
|
6
|
+
require "pakyow/support/inflector"
|
7
|
+
|
8
|
+
module Pakyow
|
9
|
+
module Assets
|
10
|
+
class Babel
|
11
|
+
def self.transform(content, **options)
|
12
|
+
context.call("Babel.transform", content, camelize_keys(options))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.context
|
18
|
+
@context ||= ExecJS.compile(
|
19
|
+
File.read(
|
20
|
+
File.expand_path("../../../../src/@babel/standalone@7.4.5/babel.min.js", __FILE__)
|
21
|
+
)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.camelize_keys(options)
|
26
|
+
Hash[options.map { |key, value|
|
27
|
+
key = Support.inflector.camelize(key)
|
28
|
+
key = key[0, 1].downcase + key[1..-1]
|
29
|
+
[key, value]
|
30
|
+
}]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|