From d901b651768aaf0527fecaf1ed01a202545bad55 Mon Sep 17 00:00:00 2001 From: Jesse Collier Date: Fri, 3 Apr 2020 16:23:12 -0400 Subject: [PATCH] Adding directive depend_on_directory `depends_on_directory` allows you to specify all files (non-recurisve) in a directory to watch for changes. This is helpful in cases where files may be dynamic or there are many files. --- CHANGELOG.md | 1 + README.md | 51 +++++++++++++++++++++++++--- lib/sprockets/directive_processor.rb | 20 ++++++++++- test/test_asset.rb | 44 ++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef859ab9..8fe7f11c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Get upgrade notes from Sprockets 3.x to 4.x at https://github.com/rails/sprocket ## Master - Fix for Ruby 2.7 keyword arguments warning in `base.rb`. [#660](https://github.com/rails/sprockets/pull/660) +- Adding new directive `depend_on_directory` [#668](https://github.com/rails/sprockets/pull/668) ## 4.0.0 diff --git a/README.md b/README.md index 9aa66240b..b9c10e4ad 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ Here is a list of the available directives: - [`link_directory`](#link_directory) - Make target directory compile and be publicly available without adding contents to current - [`link_tree`](#link_tree) - Make target tree compile and be publicly available without adding contents to current - [`depend_on`](#depend_on) - Recompile current file if target has changed +- [`depend_on_directory`](#depend_on_directory) - Recompile current file if any files in target directory has changed - [`stub`](#stub) - Ignore target file You can see what each of these does below. @@ -239,7 +240,7 @@ The first time this file is compiled the `application.js` output will be written So, if `b.js` changes it will get recompiled. However instead of having to recompile the other files from `a.js` to `z.js` since they did not change, we can use the prior intermediary files stored in the cached values . If these files were expensive to generate, then this "partial" asset cache strategy can save a lot of time. -Directives such as `require`, `link`, and `depend_on` tell Sprockets what assets need to be re-compiled when a file changes. Files are considered "fresh" based on their mtime on disk and a combination of cache keys. +Directives such as `require`, `link`, `depend_on`, and `depend_on_directory` tell Sprockets what assets need to be re-compiled when a file changes. Files are considered "fresh" based on their mtime on disk and a combination of cache keys. On Rails you can force a "clean" install by clearing the `public/assets` and `tmp/cache/assets` directories. @@ -445,11 +446,53 @@ you need to tell sprockets that it needs to re-compile the file if `bar.data` ch var bar = '<%= File.read("bar.data") %>' ``` +To depend on an entire directory containing multiple files, use `depend_on_directory` + ### depend_on_asset `depend_on_asset` *path* works like `depend_on`, but operates recursively reading the file and following the directives found. This is automatically implied if you use `link`, so consider if it just makes sense using `link` instead of `depend_on_asset`. +### depend_on_directory + +`depend_on_directory` *path* declares all files in the given *path* without +including them in the bundle. This is useful when you need to expire an +asset's cache in response to a change in multiple files in a single directory. + +All paths are relative to your declaration and must begin with `./` + +Also, your must include these directories in your [load path](guides/building_an_asset_processing_framework.md#the-load-path). + +**Example:** + +If we've got a directory called `data` with files `a.data` and `b.data` + +``` +// ./data/a.data +A +``` + +``` +// ./data/b.data +B +``` + +``` +// ./file.js.erb +//= depend_on_directory ./data +var a = '<% File.read('data/a.data') %>' +var b = '<% File.read('data/b.data') %>' +``` + +Would produce: + +```js +var a = "A"; +var b = "B"; +``` + +You can also see [Index files are proxies for folders](#index-files-are-proxies-for-folders) for another method of organizing folders that will give you more control. + ### stub `stub` *path* excludes that asset and its dependencies from the asset bundle. @@ -491,9 +534,9 @@ When you modify the `logo.png` on disk, it will force `application.css` to be recompiled so that the fingerprint will be correct in the generated asset. You can manually make sprockets depend on any other file that is generated -by sprockets by using the `depend_on` directive. Rails implements the above -feature by auto calling `depend_on` on the original asset when the `asset_url` -is used inside of an asset. +by sprockets by using the `depend_on` or `depend_on_directory` directive. Rails +implements the above feature by auto calling `depend_on` on the original asset +when the `asset_url` is used inside of an asset. ### Styling with Sass and SCSS diff --git a/lib/sprockets/directive_processor.rb b/lib/sprockets/directive_processor.rb index 27ec4ab2d..daf1fce7a 100644 --- a/lib/sprockets/directive_processor.rb +++ b/lib/sprockets/directive_processor.rb @@ -285,6 +285,24 @@ def process_depend_on_asset_directive(path) to_load(resolve(path)) end + # Allows you to state a dependency on a relative directory + # without including it. + # + # This is used for caching purposes. Any changes made to + # the dependency directory will invalidate the cache of the + # source file. + # + # This is useful if you are using ERB and File.read to pull + # in contents from multiple files in a directory. + # + # //= depend_on_directory ./data + # + def process_depend_on_directory_directive(path = ".", accept = nil) + path = expand_relative_dirname(:depend_on_directory, path) + accept = expand_accept_shorthand(accept) + resolve_paths(*@environment.stat_directory_with_dependencies(path), accept: accept) + end + # Allows dependency to be excluded from the asset bundle. # # The `path` must be a valid asset and may or may not already @@ -374,7 +392,7 @@ def resolve_paths(paths, deps, **kargs) next if subpath == @filename || stat.directory? uri, deps = @environment.resolve(subpath, **kargs) @dependencies.merge(deps) - yield uri if uri + yield uri if uri && block_given? end end diff --git a/test/test_asset.rb b/test/test_asset.rb index 8e5c3ac82..be148ee8a 100644 --- a/test/test_asset.rb +++ b/test/test_asset.rb @@ -123,6 +123,50 @@ def self.test(name, &block) end end end + + test "modify asset's dependency file in directory" do + main = fixture_path('asset/test-main.js.erb') + dep = fixture_path('asset/data/foo.txt') + begin + ::FileUtils.mkdir File.dirname(dep) + sandbox main, dep do + write(main, "//= depend_on_directory ./data\n<%= File.read('#{dep}') %>") + write(dep, "a;") + asset = asset('test-main.js') + old_digest = asset.hexdigest + old_uri = asset.uri + assert_equal "a;\n", asset.to_s + + write(dep, "b;") + asset = asset('test-main.js') + refute_equal old_digest, asset.hexdigest + refute_equal old_uri, asset.uri + assert_equal "b;\n", asset.to_s + end + ensure + ::FileUtils.rmtree File.dirname(dep) + end + end + + test "asset's dependency on directory exists" do + main = fixture_path('asset/test-missing-directory.js.erb') + dep = fixture_path('asset/data/foo.txt') + + begin + sandbox main, dep do + ::FileUtils.rmtree File.dirname(dep) + write(main, "//= depend_on_directory ./data") + assert_raises(Sprockets::ArgumentError) do + asset('test-missing-directory.js') + end + + ::FileUtils.mkdir File.dirname(dep) + assert asset('test-missing-directory.js') + end + ensure + ::FileUtils.rmtree File.dirname(dep) + end + end end class TextStaticAssetTest < Sprockets::TestCase