propshaft 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +43 -0
- data/Rakefile +11 -0
- data/lib/propshaft/assembly.rb +50 -0
- data/lib/propshaft/asset.rb +34 -0
- data/lib/propshaft/compilers/css_asset_urls.rb +13 -0
- data/lib/propshaft/compilers.rb +33 -0
- data/lib/propshaft/helper.rb +5 -0
- data/lib/propshaft/load_path.rb +42 -0
- data/lib/propshaft/processor.rb +74 -0
- data/lib/propshaft/railtie.rb +54 -0
- data/lib/propshaft/resolver/dynamic.rb +15 -0
- data/lib/propshaft/resolver/static.rb +20 -0
- data/lib/propshaft/server.rb +31 -0
- data/lib/propshaft/version.rb +3 -0
- data/lib/propshaft.rb +3 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2eb4dab520f6e9bc958d7ea65ed10531ab418ac34c3daefdcec0b4d61094bcdd
|
4
|
+
data.tar.gz: 8d76024de20625ee78b0cc8409a2e74b66ac0ccd8203ba14e263d89b649da21d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f8fbd11cd6a8a9031be35139887e1cec8e3823a4eb68e79ad61df91a307aa1222927fd8d87316c6b55df979cdd91f57bc4d653e092190a1b7933db175e44f14
|
7
|
+
data.tar.gz: 70a5ad6e8382e5dafc4bed6b26e1f9ba5008f6bd536961393b807cbb47580dbfc95c1c1cbe04b1b4954968213c2aa112ed822e156fe8f044209bade7ccc79de8
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2021 Basecamp
|
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,43 @@
|
|
1
|
+
# Propshaft
|
2
|
+
|
3
|
+
Propshaft is an asset pipeline library for Rails. It's built for era where bundling assets to save on HTTP connections is no longer urgent, where JavaScript and CSS is either compiled by dedicated Node.js bundlers or served directly to the browsers, and where increases in bandwidth has made the need for minification less pressing. These factors allow for a dramatically simpler and faster asset pipeline compared to previous options, like Sprockets.
|
4
|
+
|
5
|
+
So that's what Propshaft doesn't do. Here's what it actually does provide:
|
6
|
+
|
7
|
+
1. Configurable load path: You can register directories from multiple places in your app and gems, and reference assets from all of these paths as though they were one.
|
8
|
+
1. Digest processing: All assets in the load path will be copied (or compiled) in a precompilation step for production that also stamps all of them with a digest hash, so you can use long-expiry cache headers for better performance. The digested assets can be referred to through their logical path because the processing leaves a manifest file that provides a way to translate.
|
9
|
+
1. Development server: There's no need to precompile the assets in development. You can refer to them via the same asset_path helpers and they'll be served by a development server.
|
10
|
+
1. Basic compiler step: Propshaft was explicitly not designed to provide full transpiler capabilities. You can get that better elsewhere. But it does offer a simple input->output compiler setup that by default is used to translate `asset-path` function calls in CSS to `url(digested-asset)` instead.
|
11
|
+
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
With Rails 7+, you can start a new application with propshaft using `rails new myapp -a propshaft` (pending the merge of [rails/rails#43261](https://github.com/rails/rails/pull/43261)).
|
16
|
+
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Propshaft makes all the assets from all the paths its been configured with through `config.assets.paths` available for serving and will copy all of them into `public/assets` when precompiling. This is unlike Sprockets, which did not copy over assets that hadn't been explicitly included in one of bundled assets.
|
21
|
+
|
22
|
+
These assets can be referenced through their logical path using the normal helpers like `asset_path`, `image_tag`, `javascript_include_tag`, and all the other asset helper tags. These logical references are automatically converted into digest-aware paths in production when `assets:precompile` has been run (through a json mapping file found in `public/assets/.manifest.json`).
|
23
|
+
|
24
|
+
Additionally, Propshaft ships with a CSS function called `asset-path("image.svg")` that'll be compiled into `url("/assets/image-f2e1ec14d6856e1958083094170ca6119c529a73.svg")` when doing `assets:precompile`. This function is applied to all `.css` files.
|
25
|
+
|
26
|
+
|
27
|
+
## Migrating from Sprockets
|
28
|
+
|
29
|
+
Propshaft does a lot less than Sprockets, by design, so it might well be a fair bit of work to migrate, if it's even desirable. This is particularly true if you rely on Sprockets to provide any form of transpiling, like CoffeeScript or Sass, or if you rely on any gems that do. You'll need to either stop transpiling or use a Node-based transpiler, like those in `jsbundling-rails` and `cssbundling-rails`.
|
30
|
+
|
31
|
+
On the other hand, if you're already bundling JavaScript and CSS through a Node-based setup, then Propshaft is going to slot in easily. Since you don't need another tool to bundle or transpile. Just to digest and serve.
|
32
|
+
|
33
|
+
But for greenfield apps using the default import-map approach, Propshaft can also work well, if you're able to deal with vanilla CSS.
|
34
|
+
|
35
|
+
|
36
|
+
## Will Propshaft replace Sprockets as the Rails default?
|
37
|
+
|
38
|
+
Most likely, but Sprockets need to be supported as well for a long time to come. Plenty of apps and gems were built on Sprocket features, and they won't be migrating soon. Still working out the compatibility story. This is very much alpha software at the moment.
|
39
|
+
|
40
|
+
|
41
|
+
## License
|
42
|
+
|
43
|
+
Propshaft is released under the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "propshaft/load_path"
|
2
|
+
require "propshaft/resolver/dynamic"
|
3
|
+
require "propshaft/resolver/static"
|
4
|
+
require "propshaft/server"
|
5
|
+
require "propshaft/processor"
|
6
|
+
require "propshaft/compilers"
|
7
|
+
require "propshaft/compilers/css_asset_urls"
|
8
|
+
|
9
|
+
class Propshaft::Assembly
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@config = config
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_path
|
17
|
+
Propshaft::LoadPath.new(config.paths)
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolver
|
21
|
+
if manifest_path.exist?
|
22
|
+
Propshaft::Resolver::Static.new manifest_path: manifest_path, prefix: config.prefix
|
23
|
+
else
|
24
|
+
Propshaft::Resolver::Dynamic.new load_path: load_path, prefix: config.prefix
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def server
|
29
|
+
Propshaft::Server.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def processor
|
33
|
+
Propshaft::Processor.new \
|
34
|
+
load_path: load_path, output_path: config.output_path, compilers: compilers
|
35
|
+
end
|
36
|
+
|
37
|
+
def compilers
|
38
|
+
@compilers ||=
|
39
|
+
Propshaft::Compilers.new(self).tap do |compilers|
|
40
|
+
Array(config.compilers).each do |(mime_type, klass)|
|
41
|
+
compilers.register mime_type, klass
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def manifest_path
|
48
|
+
config.output_path.join(Propshaft::Processor::MANIFEST_FILENAME)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require "action_dispatch/http/mime_type"
|
3
|
+
|
4
|
+
class Propshaft::Asset
|
5
|
+
attr_reader :path, :logical_path
|
6
|
+
|
7
|
+
def initialize(path, logical_path:)
|
8
|
+
@path, @logical_path = path, Pathname.new(logical_path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
File.binread(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def content_type
|
16
|
+
Mime::Type.lookup_by_extension(logical_path.extname.from(1))
|
17
|
+
end
|
18
|
+
|
19
|
+
def length
|
20
|
+
content.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def digest
|
24
|
+
Digest::SHA1.hexdigest(content)
|
25
|
+
end
|
26
|
+
|
27
|
+
def digested_path
|
28
|
+
logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other_asset)
|
32
|
+
logical_path.hash == other_asset.logical_path.hash
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Propshaft::Compilers::CssAssetUrls
|
2
|
+
attr_reader :assembly
|
3
|
+
|
4
|
+
def initialize(assembly)
|
5
|
+
@assembly = assembly
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile(input)
|
9
|
+
input.gsub(/asset-path\(["']([^"')]+)["']\)/) do |match|
|
10
|
+
%[url("/#{assembly.config.prefix}/#{assembly.load_path.find($1).digested_path}")]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Propshaft::Compilers
|
2
|
+
attr_reader :registrations, :assembly
|
3
|
+
|
4
|
+
def initialize(assembly)
|
5
|
+
@assembly = assembly
|
6
|
+
@registrations = Hash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(mime_type, klass)
|
10
|
+
registrations[mime_type] ||= []
|
11
|
+
registrations[mime_type] << klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def any?
|
15
|
+
registrations.any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def compilable?(asset)
|
19
|
+
registrations[asset.content_type.to_s].present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def compile(asset)
|
23
|
+
if relevant_registrations = registrations[asset.content_type.to_s]
|
24
|
+
asset.content.dup.tap do |input|
|
25
|
+
relevant_registrations.each do |compiler|
|
26
|
+
input.replace compiler.new(assembly).compile(input)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
asset.content
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "propshaft/asset"
|
2
|
+
|
3
|
+
class Propshaft::LoadPath
|
4
|
+
attr_reader :paths
|
5
|
+
|
6
|
+
def initialize(paths = [])
|
7
|
+
@paths = Array(paths).collect { |path| Pathname.new(path) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(asset_name)
|
11
|
+
assets_by_path[asset_name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def assets
|
15
|
+
assets_by_path.values
|
16
|
+
end
|
17
|
+
|
18
|
+
def manifest
|
19
|
+
Hash.new.tap do |manifest|
|
20
|
+
assets.each do |asset|
|
21
|
+
manifest[asset.logical_path.to_s] = asset.digested_path.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def assets_by_path
|
28
|
+
Hash.new.tap do |mapped|
|
29
|
+
paths.each do |path|
|
30
|
+
all_files_from_tree(path).each do |file|
|
31
|
+
logical_path = file.relative_path_from(path)
|
32
|
+
|
33
|
+
mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_files_from_tree(path)
|
40
|
+
path.children.flat_map { |child| child.directory? ? all_files_from_tree(child) : child }
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Propshaft::Processor
|
2
|
+
MANIFEST_FILENAME = ".manifest.json"
|
3
|
+
|
4
|
+
attr_reader :load_path, :output_path, :compilers
|
5
|
+
|
6
|
+
def initialize(load_path:, output_path:, compilers:)
|
7
|
+
@load_path, @output_path = load_path, output_path
|
8
|
+
@compilers = compilers
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
ensure_output_path_exists
|
13
|
+
write_manifest
|
14
|
+
output_assets
|
15
|
+
compress_assets
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def ensure_output_path_exists
|
20
|
+
FileUtils.mkdir_p output_path
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def write_manifest
|
25
|
+
File.open(output_path.join(MANIFEST_FILENAME), "wb+") do |manifest|
|
26
|
+
manifest.write load_path.manifest.to_json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def output_assets
|
32
|
+
load_path.assets.each do |asset|
|
33
|
+
unless output_path.join(asset.digested_path).exist?
|
34
|
+
FileUtils.mkdir_p output_path.join(asset.digested_path.parent)
|
35
|
+
output_asset(asset)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_asset(asset)
|
41
|
+
compile_asset(asset) || copy_asset(asset)
|
42
|
+
end
|
43
|
+
|
44
|
+
def copy_asset(asset)
|
45
|
+
FileUtils.copy asset.path, output_path.join(asset.digested_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile_asset(asset)
|
49
|
+
File.open(output_path.join(asset.digested_path), "w+") do |file|
|
50
|
+
begin
|
51
|
+
file.write compilers.compile(asset)
|
52
|
+
rescue Encoding::UndefinedConversionError
|
53
|
+
# FIXME: Not sure if there's a better way here?
|
54
|
+
file.write compilers.compile(asset).force_encoding("UTF-8")
|
55
|
+
end
|
56
|
+
end if compilers.compilable?(asset)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def compress_assets
|
61
|
+
# FIXME: Only try to compress text assets with brotli
|
62
|
+
load_path.assets.each do |asset|
|
63
|
+
compress_asset output_path.join(asset.digested_path)
|
64
|
+
end if compressor_available?
|
65
|
+
end
|
66
|
+
|
67
|
+
def compress_asset(path)
|
68
|
+
`brotli #{path} -o #{path}.br` unless Pathname.new(path.to_s + ".br").exist?
|
69
|
+
end
|
70
|
+
|
71
|
+
def compressor_available?
|
72
|
+
`which brotli`.present?
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "rails/railtie"
|
3
|
+
require "active_support/ordered_options"
|
4
|
+
|
5
|
+
# FIXME: There's gotta be a better way than this hack?
|
6
|
+
class Rails::Engine < Rails::Railtie
|
7
|
+
initializer :append_assets_path, group: :all do |app|
|
8
|
+
app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
|
9
|
+
app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
|
10
|
+
app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Propshaft
|
15
|
+
class Railtie < ::Rails::Railtie
|
16
|
+
config.assets = ActiveSupport::OrderedOptions.new
|
17
|
+
config.assets.paths = []
|
18
|
+
config.assets.prefix = "/assets"
|
19
|
+
config.assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ]
|
20
|
+
|
21
|
+
# Compatibility shiming (need to provide log warnings when used)
|
22
|
+
config.assets.precompile = []
|
23
|
+
config.assets.debug = nil
|
24
|
+
config.assets.quiet = nil
|
25
|
+
config.assets.compile = nil
|
26
|
+
config.assets.version = nil
|
27
|
+
config.assets.css_compressor = nil
|
28
|
+
config.assets.js_compressor = nil
|
29
|
+
|
30
|
+
config.after_initialize do |app|
|
31
|
+
config.assets.output_path ||=
|
32
|
+
Pathname.new(File.join(app.config.paths["public"].first, app.config.assets.prefix))
|
33
|
+
|
34
|
+
app.assets = Propshaft::Assembly.new(app.config.assets)
|
35
|
+
|
36
|
+
app.routes.prepend do
|
37
|
+
mount app.assets.server => app.assets.config.prefix
|
38
|
+
end
|
39
|
+
|
40
|
+
ActiveSupport.on_load(:action_view) do
|
41
|
+
include Propshaft::Helper
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
rake_tasks do |app|
|
46
|
+
namespace :assets do
|
47
|
+
desc "Compile all the assets from config.assets.paths"
|
48
|
+
task precompile: :environment do
|
49
|
+
Rails.application.assets.processor.process
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Propshaft::Resolver
|
2
|
+
class Dynamic
|
3
|
+
attr_reader :load_path, :prefix
|
4
|
+
|
5
|
+
def initialize(load_path:, prefix:)
|
6
|
+
@load_path, @prefix = load_path, prefix
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolve(logical_path)
|
10
|
+
if asset = load_path.find(logical_path)
|
11
|
+
File.join prefix, asset.logical_path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Propshaft::Resolver
|
2
|
+
class Static
|
3
|
+
attr_reader :manifest_path, :prefix
|
4
|
+
|
5
|
+
def initialize(manifest_path:, prefix:)
|
6
|
+
@manifest_path, @prefix = manifest_path, prefix
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolve(logical_path)
|
10
|
+
if asset_path = parsed_manifest[logical_path]
|
11
|
+
File.join prefix, asset_path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def parsed_manifest
|
17
|
+
@parsed_manifest ||= JSON.parse(manifest_path.read)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rack/utils"
|
2
|
+
|
3
|
+
class Propshaft::Server
|
4
|
+
def initialize(assembly)
|
5
|
+
@assembly = assembly
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
if asset = @assembly.load_path.find(requested_path(env))
|
10
|
+
compiled_content = @assembly.compilers.compile(asset)
|
11
|
+
|
12
|
+
[
|
13
|
+
200,
|
14
|
+
{
|
15
|
+
"Content-Length" => compiled_content.length.to_s,
|
16
|
+
"Content-Type" => asset.content_type,
|
17
|
+
"ETag" => asset.digest,
|
18
|
+
"Cache-Control" => "public, must-revalidate"
|
19
|
+
},
|
20
|
+
[ compiled_content ]
|
21
|
+
]
|
22
|
+
else
|
23
|
+
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Not found" ] ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def requested_path(env)
|
29
|
+
Rack::Utils.unescape(env["PATH_INFO"].to_s.sub(/^\//, ""))
|
30
|
+
end
|
31
|
+
end
|
data/lib/propshaft.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: propshaft
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Heinemeier Hansson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.0.0.alpha2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.0.alpha2
|
27
|
+
description:
|
28
|
+
email: dhh@hey.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- MIT-LICENSE
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- lib/propshaft.rb
|
37
|
+
- lib/propshaft/assembly.rb
|
38
|
+
- lib/propshaft/asset.rb
|
39
|
+
- lib/propshaft/compilers.rb
|
40
|
+
- lib/propshaft/compilers/css_asset_urls.rb
|
41
|
+
- lib/propshaft/helper.rb
|
42
|
+
- lib/propshaft/load_path.rb
|
43
|
+
- lib/propshaft/processor.rb
|
44
|
+
- lib/propshaft/railtie.rb
|
45
|
+
- lib/propshaft/resolver/dynamic.rb
|
46
|
+
- lib/propshaft/resolver/static.rb
|
47
|
+
- lib/propshaft/server.rb
|
48
|
+
- lib/propshaft/version.rb
|
49
|
+
homepage: https://github.com/rails/propshaft
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.7.0
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubygems_version: 3.1.4
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Deliver assets for Rails.
|
72
|
+
test_files: []
|