ultra-lite-hub 0.0.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 +7 -0
- data/roda-3.105.0/MIT-LICENSE +21 -0
- data/roda-3.105.0/lib/roda/cache.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
- data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
- data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
- data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
- data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
- data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
- data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
- data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
- data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
- data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
- data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
- data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
- data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
- data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins.rb +68 -0
- data/roda-3.105.0/lib/roda/request.rb +722 -0
- data/roda-3.105.0/lib/roda/response.rb +221 -0
- data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
- data/roda-3.105.0/lib/roda/version.rb +18 -0
- data/roda-3.105.0/lib/roda.rb +586 -0
- data/ultra-lite-hub.gemspec +12 -0
- metadata +201 -0
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The assets plugin adds support for rendering your CSS and javascript
|
|
7
|
+
# asset files on the fly in development, and compiling them
|
|
8
|
+
# to a single, compressed file in production.
|
|
9
|
+
#
|
|
10
|
+
# This uses the render plugin for rendering the assets, and the render
|
|
11
|
+
# plugin uses tilt internally, so you can use any template engine
|
|
12
|
+
# supported by tilt for your assets. Tilt ships with support for
|
|
13
|
+
# the following asset template engines, assuming the necessary libraries
|
|
14
|
+
# are installed:
|
|
15
|
+
#
|
|
16
|
+
# css :: Less, Sass, Scss
|
|
17
|
+
# js :: CoffeeScript
|
|
18
|
+
#
|
|
19
|
+
# You can also use opal as a javascript template engine, assuming it is
|
|
20
|
+
# installed.
|
|
21
|
+
#
|
|
22
|
+
# == Usage
|
|
23
|
+
#
|
|
24
|
+
# When loading the plugin, use the :css and :js options
|
|
25
|
+
# to set the source file(s) to use for CSS and javascript assets:
|
|
26
|
+
#
|
|
27
|
+
# plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
|
|
28
|
+
#
|
|
29
|
+
# This will look for the following files:
|
|
30
|
+
#
|
|
31
|
+
# assets/css/some_file.scss
|
|
32
|
+
# assets/js/some_file.coffee
|
|
33
|
+
#
|
|
34
|
+
# The values for the :css and :js options can be arrays to load multiple
|
|
35
|
+
# files. If you want to change the paths where asset files are stored, see the
|
|
36
|
+
# Options section below.
|
|
37
|
+
#
|
|
38
|
+
# === Serving
|
|
39
|
+
#
|
|
40
|
+
# In your routes, call the +r.assets+ method to add a route to your assets,
|
|
41
|
+
# which will make your app serve the rendered assets:
|
|
42
|
+
#
|
|
43
|
+
# route do |r|
|
|
44
|
+
# r.assets
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# You should generally call +r.assets+ inside the route block itself, and not
|
|
48
|
+
# under any branches of the routing tree.
|
|
49
|
+
#
|
|
50
|
+
# === Views
|
|
51
|
+
#
|
|
52
|
+
# In your layout view, use the assets method to add links to your CSS and
|
|
53
|
+
# javascript assets:
|
|
54
|
+
#
|
|
55
|
+
# <%= assets(:css) %>
|
|
56
|
+
# <%= assets(:js) %>
|
|
57
|
+
#
|
|
58
|
+
# You can add attributes to the tags by using an options hash:
|
|
59
|
+
#
|
|
60
|
+
# <%= assets(:css, media: 'print') %>
|
|
61
|
+
#
|
|
62
|
+
# The assets method will respect the application's +:add_script_name+ option,
|
|
63
|
+
# if it is set it will automatically prefix the path with the +SCRIPT_NAME+ for
|
|
64
|
+
# the request.
|
|
65
|
+
#
|
|
66
|
+
# == Asset Paths
|
|
67
|
+
#
|
|
68
|
+
# If you just want the paths rather than the full tags, you can use
|
|
69
|
+
# assets_paths instead. This will return an array of the sources that
|
|
70
|
+
# the assets function would have put into tags:
|
|
71
|
+
#
|
|
72
|
+
# assets_paths(:css)
|
|
73
|
+
# # => ["/assets/css/foo.css", "/assets/css/app.css"]
|
|
74
|
+
#
|
|
75
|
+
# If compilation is turned on, it will return the path to the compiled
|
|
76
|
+
# asset:
|
|
77
|
+
#
|
|
78
|
+
# assets_paths(:css)
|
|
79
|
+
# # => ["/assets/app.5e7b06baa1a514d8473b0eca514b806c201073b9.css"]
|
|
80
|
+
#
|
|
81
|
+
# == Asset Groups
|
|
82
|
+
#
|
|
83
|
+
# The asset plugin supports groups for the cases where you have different
|
|
84
|
+
# css/js files for your front end and back end. To use asset groups, you
|
|
85
|
+
# pass a hash for the :css and/or :js options:
|
|
86
|
+
#
|
|
87
|
+
# plugin :assets, css: {frontend: 'some_frontend_file.scss',
|
|
88
|
+
# backend: 'some_backend_file.scss'}
|
|
89
|
+
#
|
|
90
|
+
# This expects the following directory structure for your assets:
|
|
91
|
+
#
|
|
92
|
+
# assets/css/frontend/some_frontend_file.scss
|
|
93
|
+
# assets/css/backend/some_backend_file.scss
|
|
94
|
+
#
|
|
95
|
+
# If you do not want to force that directory structure when using
|
|
96
|
+
# asset groups, you can use the <tt>group_subdirs: false</tt> option.
|
|
97
|
+
#
|
|
98
|
+
# In your view code use an array argument in your call to assets:
|
|
99
|
+
#
|
|
100
|
+
# <%= assets([:css, :frontend]) %>
|
|
101
|
+
#
|
|
102
|
+
# === Nesting
|
|
103
|
+
#
|
|
104
|
+
# Asset groups also support nesting, though that should only be needed
|
|
105
|
+
# in fairly large applications. You can use a nested hash when loading
|
|
106
|
+
# the plugin:
|
|
107
|
+
#
|
|
108
|
+
# plugin :assets,
|
|
109
|
+
# css: {frontend: {dashboard: 'some_frontend_file.scss'}}
|
|
110
|
+
#
|
|
111
|
+
# and an extra entry per nesting level when creating the tags.
|
|
112
|
+
#
|
|
113
|
+
# <%= assets([:css, :frontend, :dashboard]) %>
|
|
114
|
+
#
|
|
115
|
+
# == Caching
|
|
116
|
+
#
|
|
117
|
+
# The assets plugin uses the caching plugin internally, and will set the
|
|
118
|
+
# Last-Modified header to the modified timestamp of the asset source file
|
|
119
|
+
# when rendering the asset.
|
|
120
|
+
#
|
|
121
|
+
# If you have assets that include other asset files, such as using @import
|
|
122
|
+
# in a sass file, you need to specify the dependencies for your assets so
|
|
123
|
+
# that the assets plugin will correctly pick up changes. You can do this
|
|
124
|
+
# using the :dependencies option to the plugin, which takes a hash where
|
|
125
|
+
# the keys are paths to asset files, and values are arrays of paths to
|
|
126
|
+
# dependencies of those asset files:
|
|
127
|
+
#
|
|
128
|
+
# app.plugin :assets,
|
|
129
|
+
# dependencies: {'assets/css/bootstrap.scss'=>Dir['assets/css/bootstrap/' '**/*.scss']}
|
|
130
|
+
#
|
|
131
|
+
# == Asset Compilation
|
|
132
|
+
#
|
|
133
|
+
# In production, you are generally going to want to compile your assets
|
|
134
|
+
# into a single file, with you can do by calling compile_assets after
|
|
135
|
+
# loading the plugin:
|
|
136
|
+
#
|
|
137
|
+
# plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
|
|
138
|
+
# compile_assets
|
|
139
|
+
#
|
|
140
|
+
# After calling compile_assets, calls to assets in your views will default
|
|
141
|
+
# to a using a single link each to your CSS and javascript compiled asset
|
|
142
|
+
# files. By default the compiled files are written to the public directory,
|
|
143
|
+
# so that they can be served by the webserver.
|
|
144
|
+
#
|
|
145
|
+
# === Asset Compression
|
|
146
|
+
#
|
|
147
|
+
# If you have the yuicompressor gem installed and working, it will be used
|
|
148
|
+
# automatically to compress your javascript and css assets. For javascript
|
|
149
|
+
# assets, if yuicompressor is not available, the plugin will check for
|
|
150
|
+
# closure-compiler, uglifier, and minjs and use the first one that works.
|
|
151
|
+
# If no compressors are available, the assets will just be concatenated
|
|
152
|
+
# together and not compressed during compilation. You can use the
|
|
153
|
+
# :css_compressor and :js_compressor options to specify the compressor to use.
|
|
154
|
+
#
|
|
155
|
+
# It is also possible to use the built-in compression options in the CSS or JS
|
|
156
|
+
# compiler, assuming the compiler supports such options. For example, with
|
|
157
|
+
# sass/sassc, you can use:
|
|
158
|
+
#
|
|
159
|
+
# plugin :assets,
|
|
160
|
+
# css_opts: {style: :compressed}
|
|
161
|
+
#
|
|
162
|
+
# === Source Maps (CSS)
|
|
163
|
+
#
|
|
164
|
+
# The assets plugin does not have direct support for source maps, so it is
|
|
165
|
+
# recommended you use embedded source maps if supported by the CSS compiler.
|
|
166
|
+
# For sass/sassc, you can use:
|
|
167
|
+
#
|
|
168
|
+
# plugin :assets,
|
|
169
|
+
# css_opts: {:source_map_embed=>true, source_map_contents: true, source_map_file: "."}
|
|
170
|
+
#
|
|
171
|
+
# === With Asset Groups
|
|
172
|
+
#
|
|
173
|
+
# When using asset groups, a separate compiled file will be produced per
|
|
174
|
+
# asset group.
|
|
175
|
+
#
|
|
176
|
+
# === Unique Asset Names
|
|
177
|
+
#
|
|
178
|
+
# When compiling assets, a unique name is given to each asset file, using the
|
|
179
|
+
# a SHA1 hash of the content of the file. This is done so that clients do
|
|
180
|
+
# not attempt to use cached versions of the assets if the asset has changed.
|
|
181
|
+
#
|
|
182
|
+
# === Serving
|
|
183
|
+
#
|
|
184
|
+
# When compiling assets, +r.assets+ will serve the compiled asset
|
|
185
|
+
# files. However, it is recommended to have the main webserver (e.g. nginx)
|
|
186
|
+
# serve the compiled files, instead of relying on the application.
|
|
187
|
+
#
|
|
188
|
+
# Assuming you are using compiled assets in production mode that are served
|
|
189
|
+
# by the webserver, you can remove the serving of them by the application:
|
|
190
|
+
#
|
|
191
|
+
# route do |r|
|
|
192
|
+
# r.assets unless ENV['RACK_ENV'] == 'production'
|
|
193
|
+
# end
|
|
194
|
+
#
|
|
195
|
+
# If you do have the application serve the compiled assets, it will use the
|
|
196
|
+
# Last-Modified header to make sure that clients do not redownload compiled
|
|
197
|
+
# assets that haven't changed.
|
|
198
|
+
#
|
|
199
|
+
# === Asset Precompilation
|
|
200
|
+
#
|
|
201
|
+
# If you want to precompile your assets, so they do not need to be compiled
|
|
202
|
+
# every time you boot the application, you can provide a :precompiled option
|
|
203
|
+
# when loading the plugin. The value of this option should be the filename
|
|
204
|
+
# where the compiled asset metadata is stored.
|
|
205
|
+
#
|
|
206
|
+
# If the compiled asset metadata file does not exist when the assets plugin
|
|
207
|
+
# is loaded, the plugin will run in non-compiled mode. However, when you call
|
|
208
|
+
# compile_assets, it will write the compiled asset metadata file after
|
|
209
|
+
# compiling the assets.
|
|
210
|
+
#
|
|
211
|
+
# If the compiled asset metadata file already exists when the assets plugin
|
|
212
|
+
# is loaded, the plugin will read the file to get the compiled asset metadata,
|
|
213
|
+
# and it will run in compiled mode, assuming that the compiled asset files
|
|
214
|
+
# already exist.
|
|
215
|
+
#
|
|
216
|
+
# ==== On Heroku
|
|
217
|
+
#
|
|
218
|
+
# Heroku supports precompiling the assets when using Roda. You just need to
|
|
219
|
+
# add an assets:precompile task, similar to this:
|
|
220
|
+
#
|
|
221
|
+
# namespace :assets do
|
|
222
|
+
# desc "Precompile the assets"
|
|
223
|
+
# task :precompile do
|
|
224
|
+
# require './app'
|
|
225
|
+
# App.compile_assets
|
|
226
|
+
# end
|
|
227
|
+
# end
|
|
228
|
+
#
|
|
229
|
+
# == Postprocessing
|
|
230
|
+
#
|
|
231
|
+
# If you pass a callable object to the :postprocessor option, it will be called
|
|
232
|
+
# before an asset is served.
|
|
233
|
+
# If the assets are to be compiled, the object will be called at compilation time.
|
|
234
|
+
#
|
|
235
|
+
# It is passed three arguments; the name of the asset file, the type of the
|
|
236
|
+
# asset file (which is a symbol, either :css or :js), and the asset contents.
|
|
237
|
+
#
|
|
238
|
+
# It should return the new content for the asset.
|
|
239
|
+
#
|
|
240
|
+
# You can use this to call Autoprefixer on your CSS:
|
|
241
|
+
#
|
|
242
|
+
# plugin :assets, {
|
|
243
|
+
# css: [ 'style.scss' ],
|
|
244
|
+
# postprocessor: lambda do |file, type, content|
|
|
245
|
+
# type == :css ? AutoprefixerRails.process(content).css : content
|
|
246
|
+
# end
|
|
247
|
+
# }
|
|
248
|
+
#
|
|
249
|
+
# == External Assets/Assets from Gems
|
|
250
|
+
#
|
|
251
|
+
# The assets plugin only supports loading assets files underneath the assets
|
|
252
|
+
# path. You cannot pass an absolute path to an asset file and have it
|
|
253
|
+
# work. If you would like to reference asset files that are outside the assets
|
|
254
|
+
# path, you have the following options:
|
|
255
|
+
#
|
|
256
|
+
# * Copy, hard link, or symlink the external assets files into the assets path.
|
|
257
|
+
# * Use tilt-indirect or another method of indirection (such as an erb template that loads
|
|
258
|
+
# the external asset file) so that a file inside the assets path can reference files
|
|
259
|
+
# outside the assets path.
|
|
260
|
+
#
|
|
261
|
+
# == Plugin Options
|
|
262
|
+
#
|
|
263
|
+
# :add_suffix :: Whether to append a .css or .js extension to asset routes in non-compiled mode
|
|
264
|
+
# (default: false)
|
|
265
|
+
# :compiled_asset_host :: The asset host to use for compiled assets. Should include the protocol
|
|
266
|
+
# as well as the host (e.g. "https://cdn.example.com", "//cdn.example.com")
|
|
267
|
+
# :compiled_css_dir :: Directory name in which to store the compiled css file,
|
|
268
|
+
# inside :compiled_path (default: nil)
|
|
269
|
+
# :compiled_css_route :: Route under :prefix for compiled css assets (default: :compiled_css_dir)
|
|
270
|
+
# :compiled_js_dir :: Directory name in which to store the compiled javascript file,
|
|
271
|
+
# inside :compiled_path (default: nil)
|
|
272
|
+
# :compiled_js_route :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
|
|
273
|
+
# :compiled_name :: Compiled file name prefix (default: 'app')
|
|
274
|
+
# :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
|
|
275
|
+
# :concat_only :: Whether to just concatenate instead of concatenating
|
|
276
|
+
# and compressing files (default: false)
|
|
277
|
+
# :css_compressor :: Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try
|
|
278
|
+
# :yui if available, but not fail if it is not available)
|
|
279
|
+
# :css_dir :: Directory name containing your css source, inside :path (default: 'css')
|
|
280
|
+
# :css_headers :: A hash of additional headers for your rendered css files
|
|
281
|
+
# :css_opts :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
|
|
282
|
+
# :css_route :: Route under :prefix for css assets (default: :css_dir)
|
|
283
|
+
# :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
|
|
284
|
+
# values should be arrays of paths your asset files depends on. This is used to
|
|
285
|
+
# detect changes in your asset files.
|
|
286
|
+
# :early_hints :: Automatically send early hints for all assets. Requires the early_hints plugin.
|
|
287
|
+
# :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
|
|
288
|
+
# related group are contained in a subdirectory with the same name (default: true)
|
|
289
|
+
# :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
|
|
290
|
+
# :headers :: A hash of additional headers for both js and css rendered files
|
|
291
|
+
# :js_compressor :: Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs,
|
|
292
|
+
# :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
|
|
293
|
+
# not fail if any of them is not available)
|
|
294
|
+
# :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
|
|
295
|
+
# :js_headers :: A hash of additional headers for your rendered javascript files
|
|
296
|
+
# :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
|
|
297
|
+
# :js_route :: Route under :prefix for javascript assets (default: :js_dir)
|
|
298
|
+
# :path :: Path to your asset source directory (default: 'assets'). Relative
|
|
299
|
+
# paths will be considered relative to the application's :root option.
|
|
300
|
+
# :postprocessor :: A block which should accept three arguments (asset name, asset type,
|
|
301
|
+
# content). This block can be used to hook into the asset system and
|
|
302
|
+
# make your own modifications before the asset is served. If the asset
|
|
303
|
+
# is to be compiled, the block is called at compile time.
|
|
304
|
+
# :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
|
|
305
|
+
# :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
|
|
306
|
+
# mode using the metadata in the file. If the file does not exist, will use
|
|
307
|
+
# non-compiled mode, but will write the metadata to the file if compile_assets is called.
|
|
308
|
+
# :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
|
|
309
|
+
# paths will be considered relative to the application's :root option.
|
|
310
|
+
# :relative_paths :: Use relative paths instead of absolute paths when setting up link and script tags for
|
|
311
|
+
# assets.
|
|
312
|
+
# :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
|
|
313
|
+
# :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
|
|
314
|
+
# hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
|
|
315
|
+
# can use nil to disable subresource integrity.
|
|
316
|
+
# :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
|
|
317
|
+
# slow down development requests due to additional requests to get last modified times,
|
|
318
|
+
# but it will make sure the paths change in development when there are modifications,
|
|
319
|
+
# which can fix issues when using a caching proxy in non-compiled mode. This can also
|
|
320
|
+
# be specified as a string to use that string to separate the timestamp from the asset.
|
|
321
|
+
# By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
|
|
322
|
+
module Assets
|
|
323
|
+
DEFAULTS = {
|
|
324
|
+
:compiled_name => 'app'.freeze,
|
|
325
|
+
:js_dir => 'js'.freeze,
|
|
326
|
+
:css_dir => 'css'.freeze,
|
|
327
|
+
:prefix => 'assets'.freeze,
|
|
328
|
+
:concat_only => false,
|
|
329
|
+
:compiled => false,
|
|
330
|
+
:add_suffix => false,
|
|
331
|
+
:early_hints => false,
|
|
332
|
+
:timestamp_paths => false,
|
|
333
|
+
:group_subdirs => true,
|
|
334
|
+
:compiled_css_dir => nil,
|
|
335
|
+
:compiled_js_dir => nil,
|
|
336
|
+
:sri => :sha256
|
|
337
|
+
}.freeze
|
|
338
|
+
|
|
339
|
+
# Internal exception raised when a compressor cannot be found
|
|
340
|
+
CompressorNotFound = Class.new(RodaError)
|
|
341
|
+
|
|
342
|
+
# Load the render, caching, and h plugins, since the assets plugin
|
|
343
|
+
# depends on them.
|
|
344
|
+
def self.load_dependencies(app, opts = OPTS)
|
|
345
|
+
app.plugin :render
|
|
346
|
+
app.plugin :caching
|
|
347
|
+
app.plugin :h
|
|
348
|
+
|
|
349
|
+
if opts[:relative_paths]
|
|
350
|
+
app.plugin :relative_path
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Setup the options for the plugin. See the Assets module RDoc
|
|
355
|
+
# for a description of the supported options.
|
|
356
|
+
def self.configure(app, opts = {})
|
|
357
|
+
if app.assets_opts
|
|
358
|
+
prev_opts = app.assets_opts[:orig_opts]
|
|
359
|
+
orig_opts = app.assets_opts[:orig_opts].merge(opts)
|
|
360
|
+
[:headers, :css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
|
|
361
|
+
if prev_opts[s]
|
|
362
|
+
if opts[s]
|
|
363
|
+
orig_opts[s] = prev_opts[s].merge(opts[s])
|
|
364
|
+
else
|
|
365
|
+
orig_opts[s] = prev_opts[s].dup
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
app.opts[:assets] = orig_opts.dup
|
|
370
|
+
app.opts[:assets][:orig_opts] = orig_opts
|
|
371
|
+
else
|
|
372
|
+
app.opts[:assets] = opts.dup
|
|
373
|
+
app.opts[:assets][:orig_opts] = opts
|
|
374
|
+
end
|
|
375
|
+
opts = app.opts[:assets]
|
|
376
|
+
opts[:path] = app.expand_path(opts[:path]||"assets").freeze
|
|
377
|
+
opts[:public] = app.expand_path(opts[:public]||"public").freeze
|
|
378
|
+
|
|
379
|
+
# Combine multiple values into a path, ignoring trailing slashes
|
|
380
|
+
j = lambda do |*v|
|
|
381
|
+
opts.values_at(*v).
|
|
382
|
+
reject{|s| s.to_s.empty?}.
|
|
383
|
+
map{|s| s.chomp('/')}.
|
|
384
|
+
join('/').freeze
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Same as j, but add a trailing slash if not empty
|
|
388
|
+
sj = lambda do |*v|
|
|
389
|
+
s = j.call(*v)
|
|
390
|
+
s.empty? ? s : (s + '/').freeze
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
|
|
394
|
+
require 'json'
|
|
395
|
+
opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
if opts[:early_hints]
|
|
399
|
+
app.plugin :early_hints
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
if opts[:timestamp_paths] && !opts[:timestamp_paths].is_a?(String)
|
|
403
|
+
opts[:timestamp_paths] = '/'
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
DEFAULTS.each do |k, v|
|
|
407
|
+
opts[k] = v unless opts.has_key?(k)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
[
|
|
411
|
+
[:compiled_path, :prefix],
|
|
412
|
+
[:js_route, :js_dir],
|
|
413
|
+
[:css_route, :css_dir],
|
|
414
|
+
[:compiled_js_route, :compiled_js_dir],
|
|
415
|
+
[:compiled_css_route, :compiled_css_dir]
|
|
416
|
+
].each do |k, v|
|
|
417
|
+
opts[k] = opts[v] unless opts.has_key?(k)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
[:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
|
|
421
|
+
opts[s] ||= {}
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
expanded_deps = opts[:expanded_dependencies] = {}
|
|
425
|
+
opts[:dependencies].each do |file, deps|
|
|
426
|
+
expanded_deps[File.expand_path(file)] = Array(deps)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
if headers = opts[:headers]
|
|
430
|
+
opts[:css_headers] = headers.merge(opts[:css_headers])
|
|
431
|
+
opts[:js_headers] = headers.merge(opts[:js_headers])
|
|
432
|
+
end
|
|
433
|
+
opts[:css_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "text/css; charset=UTF-8".freeze
|
|
434
|
+
opts[:js_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "application/javascript; charset=UTF-8".freeze
|
|
435
|
+
|
|
436
|
+
[:css_headers, :js_headers, :css_opts, :js_opts, :dependencies, :expanded_dependencies].each do |s|
|
|
437
|
+
opts[s].freeze
|
|
438
|
+
end
|
|
439
|
+
[:headers, :css, :js].each do |s|
|
|
440
|
+
opts[s].freeze if opts[s]
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Used for reading/writing files
|
|
444
|
+
opts[:js_path] = sj.call(:path, :js_dir)
|
|
445
|
+
opts[:css_path] = sj.call(:path, :css_dir)
|
|
446
|
+
opts[:compiled_js_path] = j.call(:public, :compiled_path, :compiled_js_dir, :compiled_name)
|
|
447
|
+
opts[:compiled_css_path] = j.call(:public, :compiled_path, :compiled_css_dir, :compiled_name)
|
|
448
|
+
|
|
449
|
+
# Used for URLs/routes
|
|
450
|
+
opts[:js_prefix] = sj.call(:prefix, :js_route)
|
|
451
|
+
opts[:css_prefix] = sj.call(:prefix, :css_route)
|
|
452
|
+
opts[:compiled_js_prefix] = j.call(:prefix, :compiled_js_route, :compiled_name)
|
|
453
|
+
opts[:compiled_css_prefix] = j.call(:prefix, :compiled_css_route, :compiled_name)
|
|
454
|
+
opts[:js_suffix] = (opts[:add_suffix] ? '.js' : '').freeze
|
|
455
|
+
opts[:css_suffix] = (opts[:add_suffix] ? '.css' : '').freeze
|
|
456
|
+
|
|
457
|
+
opts.freeze
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
module ClassMethods
|
|
461
|
+
# Return the assets options for this class.
|
|
462
|
+
def assets_opts
|
|
463
|
+
opts[:assets]
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Compile options for the given asset type. If no asset_type
|
|
467
|
+
# is given, compile both the :css and :js asset types. You
|
|
468
|
+
# can specify an array of types (e.g. [:css, :frontend]) to
|
|
469
|
+
# compile assets for the given asset group.
|
|
470
|
+
def compile_assets(type=nil)
|
|
471
|
+
require 'fileutils'
|
|
472
|
+
|
|
473
|
+
unless assets_opts[:compiled]
|
|
474
|
+
opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
if type == nil
|
|
478
|
+
_compile_assets(:css)
|
|
479
|
+
_compile_assets(:js)
|
|
480
|
+
else
|
|
481
|
+
_compile_assets(type)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
if precompile_file = assets_opts[:precompiled]
|
|
485
|
+
require 'json'
|
|
486
|
+
::FileUtils.mkdir_p(File.dirname(precompile_file))
|
|
487
|
+
tmp_file = "#{precompile_file}.tmp"
|
|
488
|
+
::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
|
|
489
|
+
::File.rename(tmp_file, precompile_file)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
assets_opts[:compiled]
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
private
|
|
496
|
+
|
|
497
|
+
# The initial hash to use to store compiled asset metadata.
|
|
498
|
+
def _compiled_assets_initial_hash
|
|
499
|
+
{}
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Internals of compile_assets, handling recursive calls for loading
|
|
503
|
+
# all asset groups under the given type.
|
|
504
|
+
def _compile_assets(type)
|
|
505
|
+
type, *dirs = type if type.is_a?(Array)
|
|
506
|
+
dirs ||= []
|
|
507
|
+
files = assets_opts[type]
|
|
508
|
+
dirs.each{|d| files = files[d]}
|
|
509
|
+
|
|
510
|
+
case files
|
|
511
|
+
when Hash
|
|
512
|
+
files.each_key{|dir| _compile_assets([type] + dirs + [dir])}
|
|
513
|
+
else
|
|
514
|
+
files = Array(files)
|
|
515
|
+
compile_assets_files(files, type, dirs) unless files.empty?
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# The precompiled asset metadata stored in the given file
|
|
520
|
+
def _precompiled_asset_metadata(file)
|
|
521
|
+
(opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Compile each array of files for the given type into a single
|
|
525
|
+
# file. Dirs should be an array of asset group names, if these
|
|
526
|
+
# are files in an asset group.
|
|
527
|
+
def compile_assets_files(files, type, dirs)
|
|
528
|
+
dirs = nil if dirs && dirs.empty?
|
|
529
|
+
o = assets_opts
|
|
530
|
+
app = allocate
|
|
531
|
+
|
|
532
|
+
content = files.map do |file|
|
|
533
|
+
file = "#{dirs.join('/')}/#{file}" if dirs && o[:group_subdirs]
|
|
534
|
+
file = "#{o[:"#{type}_path"]}#{file}"
|
|
535
|
+
app.read_asset_file(file, type)
|
|
536
|
+
end.join("\n")
|
|
537
|
+
|
|
538
|
+
unless o[:concat_only]
|
|
539
|
+
content = compress_asset(content, type)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
suffix = ".#{dirs.join('.')}" if dirs
|
|
543
|
+
key = "#{type}#{suffix}"
|
|
544
|
+
unique_id = o[:compiled][key] = asset_digest(content)
|
|
545
|
+
path = "#{o[:"compiled_#{type}_path"]}#{suffix}.#{unique_id}.#{type}"
|
|
546
|
+
::FileUtils.mkdir_p(File.dirname(path))
|
|
547
|
+
::File.open(path, 'wb'){|f| f.write(content)}
|
|
548
|
+
|
|
549
|
+
if o[:gzip]
|
|
550
|
+
require 'zlib'
|
|
551
|
+
Zlib::GzipWriter.open("#{path}.gz") do |gz|
|
|
552
|
+
gz.write(content)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
nil
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Compress the given content for the given type by using the
|
|
560
|
+
# configured compressor, or trying the supported compressors.
|
|
561
|
+
def compress_asset(content, type)
|
|
562
|
+
case compressor = assets_opts[:"#{type}_compressor"]
|
|
563
|
+
when :none
|
|
564
|
+
return content
|
|
565
|
+
when nil
|
|
566
|
+
# default, try different compressors
|
|
567
|
+
else
|
|
568
|
+
# Allow calling private compress methods
|
|
569
|
+
return send("compress_#{type}_#{compressor}", content)
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
compressors = if type == :js
|
|
573
|
+
[:yui, :closure, :uglifier, :minjs]
|
|
574
|
+
else
|
|
575
|
+
[:yui]
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
compressors.each do |comp|
|
|
579
|
+
begin
|
|
580
|
+
# Allow calling private compress methods
|
|
581
|
+
if c = send("compress_#{type}_#{comp}", content)
|
|
582
|
+
return c
|
|
583
|
+
end
|
|
584
|
+
rescue LoadError, CompressorNotFound
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
content
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Compress the CSS using YUI Compressor, requires java runtime
|
|
592
|
+
def compress_css_yui(content)
|
|
593
|
+
compress_yui(content, :compress_css)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Compress the JS using Google Closure Compiler, requires java runtime
|
|
597
|
+
def compress_js_closure(content)
|
|
598
|
+
require 'closure-compiler'
|
|
599
|
+
|
|
600
|
+
begin
|
|
601
|
+
::Closure::Compiler.new.compile(content)
|
|
602
|
+
rescue ::Closure::Error => e
|
|
603
|
+
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Compress the JS using MinJS, a pure ruby compressor
|
|
608
|
+
def compress_js_minjs(content)
|
|
609
|
+
require 'minjs'
|
|
610
|
+
Minjs::Compressor::Compressor.new(:debug => false).compress(content).to_js
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# Compress the JS using Uglifier, requires javascript runtime
|
|
614
|
+
def compress_js_uglifier(content)
|
|
615
|
+
begin
|
|
616
|
+
require 'uglifier'
|
|
617
|
+
rescue => e
|
|
618
|
+
# :nocov:
|
|
619
|
+
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
|
|
620
|
+
# :nocov:
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
Uglifier.compile(content)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Compress the CSS using YUI Compressor, requires java runtime
|
|
627
|
+
def compress_js_yui(content)
|
|
628
|
+
compress_yui(content, :compress_js)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
# Compress the CSS/JS using YUI Compressor, requires java runtime
|
|
632
|
+
def compress_yui(content, meth)
|
|
633
|
+
require 'yuicompressor'
|
|
634
|
+
::YUICompressor.public_send(meth, content, :munge => true)
|
|
635
|
+
rescue ::Errno::ENOENT => e
|
|
636
|
+
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Return a unique id for the given content. By default, uses the
|
|
640
|
+
# SHA256 hash of the content. This method can be overridden to use
|
|
641
|
+
# a different digest type or to return a static string if you don't
|
|
642
|
+
# want to use a unique value.
|
|
643
|
+
def asset_digest(content)
|
|
644
|
+
algo = assets_opts[:sri] || :sha256
|
|
645
|
+
digest = begin
|
|
646
|
+
require 'openssl'
|
|
647
|
+
::OpenSSL::Digest
|
|
648
|
+
# :nocov:
|
|
649
|
+
rescue LoadError
|
|
650
|
+
require 'digest/sha2'
|
|
651
|
+
::Digest
|
|
652
|
+
# :nocov:
|
|
653
|
+
end
|
|
654
|
+
digest.const_get(algo.to_s.upcase).hexdigest(content)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
module InstanceMethods
|
|
659
|
+
# Return an array of paths for the given asset type and optionally
|
|
660
|
+
# asset group. See the assets function documentation for details.
|
|
661
|
+
def assets_paths(type)
|
|
662
|
+
o = self.class.assets_opts
|
|
663
|
+
if type.is_a?(Array)
|
|
664
|
+
ltype, *dirs = type
|
|
665
|
+
else
|
|
666
|
+
ltype = type
|
|
667
|
+
end
|
|
668
|
+
stype = ltype.to_s
|
|
669
|
+
|
|
670
|
+
url_prefix = request.script_name if self.class.opts[:add_script_name]
|
|
671
|
+
relative_paths = o[:relative_paths]
|
|
672
|
+
|
|
673
|
+
paths = if o[:compiled]
|
|
674
|
+
relative_paths = false if o[:compiled_asset_host]
|
|
675
|
+
if ukey = _compiled_assets_hash(type, true)
|
|
676
|
+
["#{o[:compiled_asset_host]}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
|
|
677
|
+
else
|
|
678
|
+
[]
|
|
679
|
+
end
|
|
680
|
+
else
|
|
681
|
+
asset_dir = o[ltype]
|
|
682
|
+
if dirs && !dirs.empty?
|
|
683
|
+
dirs.each{|f| asset_dir = asset_dir[f]}
|
|
684
|
+
prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
|
|
685
|
+
end
|
|
686
|
+
Array(asset_dir).map do |f|
|
|
687
|
+
if ts = o[:timestamp_paths]
|
|
688
|
+
mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
|
|
689
|
+
mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}#{ts}"
|
|
690
|
+
end
|
|
691
|
+
"#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
if relative_paths
|
|
696
|
+
paths.map! do |path|
|
|
697
|
+
"#{relative_prefix}#{path}"
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
paths
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# Return a string containing html tags for the given asset type.
|
|
705
|
+
# This will use a script tag for the :js type and a link tag for
|
|
706
|
+
# the :css type.
|
|
707
|
+
#
|
|
708
|
+
# To return the tags for a specific asset group, use an array for
|
|
709
|
+
# the type, such as [:css, :frontend].
|
|
710
|
+
#
|
|
711
|
+
# You can specify custom attributes for the tag by passing a hash
|
|
712
|
+
# as the attrs argument.
|
|
713
|
+
#
|
|
714
|
+
# When the assets are not compiled, this will result in a separate
|
|
715
|
+
# tag for each asset file. When the assets are compiled, this will
|
|
716
|
+
# result in a single tag to the compiled asset file.
|
|
717
|
+
def assets(type, attrs = OPTS)
|
|
718
|
+
ltype = type.is_a?(Array) ? type[0] : type
|
|
719
|
+
|
|
720
|
+
o = self.class.assets_opts
|
|
721
|
+
if o[:compiled] && (algo = o[:sri]) && (hash = _compiled_assets_hash(type))
|
|
722
|
+
attrs = Hash[attrs]
|
|
723
|
+
attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
attributes = attrs.map{|k,v| "#{k}=\"#{h(v)}\""}.join(' ')
|
|
727
|
+
|
|
728
|
+
if ltype == :js
|
|
729
|
+
tag_start = "<script#{' type="text/javascript"' unless attrs[:type]} #{attributes} src=\""
|
|
730
|
+
tag_end = "\"></script>"
|
|
731
|
+
else
|
|
732
|
+
tag_start = "<link rel=\"stylesheet\" #{attributes} href=\""
|
|
733
|
+
tag_end = "\" />"
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
paths = assets_paths(type)
|
|
737
|
+
if o[:early_hints]
|
|
738
|
+
early_hint_as = ltype == :js ? 'script' : 'style'
|
|
739
|
+
early_hints = paths.map{|p| "<#{p}>; rel=preload; as=#{early_hint_as}"}
|
|
740
|
+
early_hints = early_hints.join("\n") if Rack.release < '3'
|
|
741
|
+
send_early_hints(RodaResponseHeaders::LINK=>early_hints)
|
|
742
|
+
end
|
|
743
|
+
paths.map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# Render the asset with the given filename. When assets are compiled,
|
|
747
|
+
# or when the file is already of the given type (no rendering necessary),
|
|
748
|
+
# this returns the contents of the compiled file.
|
|
749
|
+
# When assets are not compiled and the file is not already in the same format,
|
|
750
|
+
# this will render the asset using the render plugin.
|
|
751
|
+
# In both cases, if the file has not been modified since the last request,
|
|
752
|
+
# this will return a 304 response.
|
|
753
|
+
def render_asset(file, type)
|
|
754
|
+
o = self.class.assets_opts
|
|
755
|
+
if o[:compiled]
|
|
756
|
+
file = "#{o[:"compiled_#{type}_path"]}#{file}"
|
|
757
|
+
|
|
758
|
+
if o[:gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
|
|
759
|
+
@_response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
|
|
760
|
+
file += '.gz'
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
check_asset_request(file, type, ::File.stat(file).mtime)
|
|
764
|
+
::File.read(file)
|
|
765
|
+
else
|
|
766
|
+
file = "#{o[:"#{type}_path"]}#{file}"
|
|
767
|
+
check_asset_request(file, type, asset_last_modified(file))
|
|
768
|
+
read_asset_file(file, type)
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Return the content of the file if it is already of the correct type.
|
|
773
|
+
# Otherwise, render the file using the render plugin. +file+ should be
|
|
774
|
+
# the relative path to the file from the current directory.
|
|
775
|
+
def read_asset_file(file, type)
|
|
776
|
+
o = self.class.assets_opts
|
|
777
|
+
|
|
778
|
+
content = if file.end_with?(".#{type}")
|
|
779
|
+
::File.read(file)
|
|
780
|
+
else
|
|
781
|
+
render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o[:expanded_dependencies][file])
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
private
|
|
788
|
+
|
|
789
|
+
def _compiled_assets_hash(type, return_ukey=false)
|
|
790
|
+
compiled = self.class.assets_opts[:compiled]
|
|
791
|
+
type, *dirs = type if type.is_a?(Array)
|
|
792
|
+
stype = type.to_s
|
|
793
|
+
|
|
794
|
+
if dirs && !dirs.empty?
|
|
795
|
+
key = dirs.join('.')
|
|
796
|
+
ckey = "#{stype}.#{key}"
|
|
797
|
+
if hash = ukey = compiled[ckey]
|
|
798
|
+
ukey = "#{key}.#{ukey}"
|
|
799
|
+
end
|
|
800
|
+
else
|
|
801
|
+
hash = ukey = compiled[stype]
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
return_ukey ? ukey : hash
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# Return when the file was last modified. If the file depends on any
|
|
808
|
+
# other files, check the modification times of all dependencies and
|
|
809
|
+
# return the maximum.
|
|
810
|
+
def asset_last_modified(file)
|
|
811
|
+
if deps = self.class.assets_opts[:expanded_dependencies][file]
|
|
812
|
+
([file] + Array(deps)).map{|f| ::File.stat(f).mtime}.max
|
|
813
|
+
else
|
|
814
|
+
::File.stat(file).mtime
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
# If the asset hasn't been modified since the last request, return
|
|
819
|
+
# a 304 response immediately. Otherwise, add the appropriate
|
|
820
|
+
# type-specific headers.
|
|
821
|
+
def check_asset_request(file, type, mtime)
|
|
822
|
+
@_request.last_modified(mtime)
|
|
823
|
+
@_response.headers.merge!(self.class.assets_opts[:"#{type}_headers"])
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# Render the given asset file using the render plugin, with the given options.
|
|
827
|
+
# +file+ should be the relative path to the file from the current directory.
|
|
828
|
+
def render_asset_file(file, options)
|
|
829
|
+
render_template({:path => file}, options)
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
module RequestClassMethods
|
|
834
|
+
# An array of asset type strings and regexps for that type, for all asset types
|
|
835
|
+
# handled.
|
|
836
|
+
def assets_matchers
|
|
837
|
+
@assets_matchers ||= [:css, :js].map do |t|
|
|
838
|
+
if regexp = assets_regexp(t)
|
|
839
|
+
[t, regexp].freeze
|
|
840
|
+
end
|
|
841
|
+
end.compact.freeze
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
private
|
|
845
|
+
|
|
846
|
+
# A string for the asset filename for the asset type, key, and digest.
|
|
847
|
+
def _asset_regexp(type, key, digest)
|
|
848
|
+
"#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# The regexp matcher to use for the given type. This handles any asset groups
|
|
852
|
+
# for the asset types.
|
|
853
|
+
def assets_regexp(type)
|
|
854
|
+
o = roda_class.assets_opts
|
|
855
|
+
if compiled = o[:compiled]
|
|
856
|
+
assets = compiled.
|
|
857
|
+
select{|k,_| k =~ /\A#{type}/}.
|
|
858
|
+
map{|k, md| _asset_regexp(type, k, md)}
|
|
859
|
+
return if assets.empty?
|
|
860
|
+
/#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
|
|
861
|
+
else
|
|
862
|
+
return unless assets = o[type]
|
|
863
|
+
assets = unnest_assets_hash(assets)
|
|
864
|
+
ts = o[:timestamp_paths]
|
|
865
|
+
/#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# Recursively unnested the given assets hash, returning a single array of asset
|
|
870
|
+
# files for the given.
|
|
871
|
+
def unnest_assets_hash(h)
|
|
872
|
+
case h
|
|
873
|
+
when Hash
|
|
874
|
+
h.flat_map do |k,v|
|
|
875
|
+
assets = unnest_assets_hash(v)
|
|
876
|
+
assets = assets.map{|x| "#{k}/#{x}"} if roda_class.assets_opts[:group_subdirs]
|
|
877
|
+
assets
|
|
878
|
+
end
|
|
879
|
+
else
|
|
880
|
+
Array(h)
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
module RequestMethods
|
|
886
|
+
# Render the matching asset if this is a GET request for a supported asset.
|
|
887
|
+
def assets
|
|
888
|
+
if is_get?
|
|
889
|
+
self.class.assets_matchers.each do |type, matcher|
|
|
890
|
+
is matcher do |file|
|
|
891
|
+
scope.render_asset(file, type)
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
nil
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
register_plugin(:assets, Assets)
|
|
901
|
+
end
|
|
902
|
+
end
|