proscenium 0.19.0.beta6 → 0.19.0.beta7
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 +4 -4
- data/README.md +3 -26
- data/lib/proscenium/builder.rb +11 -35
- data/lib/proscenium/bundled_gems.rb +37 -0
- data/lib/proscenium/css_module/transformer.rb +1 -1
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +1 -7
- data/lib/proscenium/helper.rb +3 -9
- data/lib/proscenium/importer.rb +13 -11
- data/lib/proscenium/log_subscriber.rb +0 -12
- data/lib/proscenium/middleware/base.rb +10 -8
- data/lib/proscenium/middleware/esbuild.rb +1 -6
- data/lib/proscenium/middleware/ruby_gems.rb +23 -0
- data/lib/proscenium/middleware.rb +26 -22
- data/lib/proscenium/monkey.rb +3 -5
- data/lib/proscenium/phlex/asset_inclusions.rb +0 -1
- data/lib/proscenium/railtie.rb +0 -27
- data/lib/proscenium/registry/bundled_package.rb +29 -0
- data/lib/proscenium/registry/package.rb +95 -0
- data/lib/proscenium/registry/ruby_gem_package.rb +28 -0
- data/lib/proscenium/registry.rb +29 -0
- data/lib/proscenium/resolver.rb +23 -18
- data/lib/proscenium/ruby_gems.rb +67 -0
- data/lib/proscenium/side_load.rb +20 -63
- data/lib/proscenium/ui/flash/bun.lock +19 -0
- data/lib/proscenium/ui/flash/index.js +6 -2
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/index.d.ts +33 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/index.js +44 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/license +9 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/package.json +59 -0
- data/lib/proscenium/ui/flash/node_modules/dom-mutations/readme.md +125 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/LICENSE +20 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/README.md +11 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/package.json +44 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/src/sourdough-toast.css +697 -0
- data/lib/proscenium/ui/flash/node_modules/sourdough-toast/src/sourdough-toast.js +537 -0
- data/lib/proscenium/ui/flash/package.json +11 -0
- data/lib/proscenium/ui/react-manager/index.jsx +3 -22
- data/lib/proscenium/ui/ujs/index.js +1 -1
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium.rb +3 -4
- metadata +21 -3
- data/lib/proscenium/middleware/engines.rb +0 -41
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Proscenium::Registry
|
4
|
+
# 1. Fetch the gem metadata from RubyGems API.
|
5
|
+
# 2. Extract any package.json from the gem, and populate the response with it.
|
6
|
+
# 3. Create a tarball containing the fetched package.json. This will be downloaded by the npm
|
7
|
+
# client, and unpacked into node_modules. Proscenium ignores this, as it will pull contents
|
8
|
+
# directly from location of the installed gem.
|
9
|
+
# 4. Return a valid npm response listing package details, tarball location, and its dependencies.
|
10
|
+
#
|
11
|
+
# See https://wiki.commonjs.org/wiki/Packages/Registry
|
12
|
+
class Package
|
13
|
+
extend Literal::Properties
|
14
|
+
|
15
|
+
prop :name, String, :positional, reader: :private
|
16
|
+
prop :version, _String?
|
17
|
+
prop :host, String
|
18
|
+
|
19
|
+
def as_json
|
20
|
+
{
|
21
|
+
name:,
|
22
|
+
'dist-tags': {
|
23
|
+
latest: version
|
24
|
+
},
|
25
|
+
versions: {
|
26
|
+
version => {
|
27
|
+
name:,
|
28
|
+
version:,
|
29
|
+
dependencies: package_json['dependencies'] || {},
|
30
|
+
dist: {
|
31
|
+
tarball:,
|
32
|
+
integrity:,
|
33
|
+
shasum:
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate!
|
41
|
+
return self if name.start_with?('@rubygems/')
|
42
|
+
|
43
|
+
raise PackageUnsupportedError, name
|
44
|
+
end
|
45
|
+
|
46
|
+
def gem_name = @gem_name ||= name.gsub('@rubygems/', '')
|
47
|
+
def version = @version # rubocop:disable Style/TrivialAccessors
|
48
|
+
def shasum = Digest::SHA1.file(tarball_path).hexdigest
|
49
|
+
def integrity = "sha512-#{Digest::SHA512.file(tarball_path).base64digest}"
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def tarball
|
54
|
+
create_tarball unless tarball_path.exist?
|
55
|
+
|
56
|
+
"#{@host}/#{tarball_path.relative_path_from(Rails.public_path)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def tarball_name
|
60
|
+
@tarball_name ||= "#{gem_name}-#{version}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def tarball_path
|
64
|
+
@tarball_path ||= Rails.public_path.join('proscenium_registry_tarballs')
|
65
|
+
.join("@rubygems/#{gem_name}/#{tarball_name}.tgz")
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_tarball
|
69
|
+
FileUtils.mkdir_p(File.dirname(tarball_path))
|
70
|
+
|
71
|
+
File.open(tarball_path, 'wb') do |file|
|
72
|
+
Zlib::GzipWriter.wrap(file) do |gz|
|
73
|
+
Gem::Package::TarWriter.new(gz) do |tar|
|
74
|
+
contents = package_json.to_json
|
75
|
+
tar.add_file_simple('package/package.json', 0o444, contents.length) do |io|
|
76
|
+
io.write contents
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def package_json
|
84
|
+
@package_json ||= default_package_json
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_package_json
|
88
|
+
{
|
89
|
+
name:,
|
90
|
+
version:,
|
91
|
+
dependencies: {}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Proscenium::Registry
|
4
|
+
class RubyGemPackage < Package
|
5
|
+
def version = spec['version']
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def package_json
|
10
|
+
@package_json ||= begin
|
11
|
+
package_path = Proscenium::RubyGems.path_for(gem_name, version).join('package.json')
|
12
|
+
if package_path.exist?
|
13
|
+
JSON.parse path.read
|
14
|
+
else
|
15
|
+
default_package_json
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def spec
|
21
|
+
@spec ||= if @version.present?
|
22
|
+
Gems::V2.info gem_name, @version
|
23
|
+
else
|
24
|
+
Gems.info(gem_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Proscenium::Registry
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Package
|
7
|
+
autoload :BundledPackage
|
8
|
+
autoload :RubyGemPackage
|
9
|
+
|
10
|
+
class PackageUnsupportedError < StandardError
|
11
|
+
def initialize(name)
|
12
|
+
super("Package `#{name}` is not valid; only Ruby gems are supported via the @rubygems scope.")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class PackageNotInstalledError < StandardError
|
17
|
+
def initialize(name)
|
18
|
+
super("Package `#{name}` is not found in your bundle; have you installed the Ruby gem?")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.bundled_package(name, host:)
|
23
|
+
BundledPackage.new(name, host:).validate!
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.ruby_gem_package(name, version, host:)
|
27
|
+
RubyGemPackage.new(name, version:, host:).validate!
|
28
|
+
end
|
29
|
+
end
|
data/lib/proscenium/resolver.rb
CHANGED
@@ -4,31 +4,36 @@ require 'active_support/current_attributes'
|
|
4
4
|
|
5
5
|
module Proscenium
|
6
6
|
class Resolver < ActiveSupport::CurrentAttributes
|
7
|
-
|
8
|
-
|
7
|
+
attribute :resolved unless Rails.env.production?
|
8
|
+
mattr_accessor :resolved if Rails.env.production?
|
9
9
|
|
10
|
-
# Resolve the given `path` to a URL path.
|
10
|
+
# Resolve the given `path` to a fully qualified URL path.
|
11
11
|
#
|
12
|
-
# @param path [String]
|
12
|
+
# @param path [String] URL path, file system path, or bare specifier (ie. NPM package).
|
13
13
|
# @return [String] URL path.
|
14
14
|
def self.resolve(path)
|
15
15
|
self.resolved ||= {}
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
raise ArgumentError, 'path must be an absolute file system or URL path'
|
20
|
-
end
|
21
|
-
|
22
|
-
if path.start_with?('proscenium/')
|
23
|
-
"/#{path}"
|
24
|
-
elsif (engine = Proscenium.config.engines.find { |_, v| path.start_with? "#{v}/" })
|
25
|
-
path.sub(/^#{engine.last}/, "/#{engine.first}")
|
26
|
-
elsif path.start_with?("#{Rails.root}/")
|
27
|
-
path.delete_prefix Rails.root.to_s
|
28
|
-
else
|
29
|
-
Builder.resolve path
|
30
|
-
end
|
17
|
+
if path.start_with?('./', '../')
|
18
|
+
raise ArgumentError, '`path` must be an absolute file system or URL path'
|
31
19
|
end
|
20
|
+
|
21
|
+
self.resolved[path] ||= if (gem = BundledGems.paths.find { |_, v| path.start_with? "#{v}/" })
|
22
|
+
# If the path is a rubygem, and it is installed with npm via the
|
23
|
+
# @rubygems scope, then resolve the path to the symlinked location
|
24
|
+
# in node_modules.
|
25
|
+
# npm_path = Rails.root.join("node_modules/@rubygems/#{gem.first}")
|
26
|
+
# if npm_path.symlink?
|
27
|
+
# npm_path.realpath.join(path.sub(/^#{gem.last}/, '.')).to_s
|
28
|
+
# .delete_prefix(Rails.root.to_s)
|
29
|
+
# else
|
30
|
+
path.sub(/^#{gem.last}/, "/node_modules/@rubygems/#{gem.first}")
|
31
|
+
# end
|
32
|
+
elsif path.start_with?("#{Rails.root}/")
|
33
|
+
path.delete_prefix Rails.root.to_s
|
34
|
+
else
|
35
|
+
Builder.resolve path
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|
34
39
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems/package'
|
4
|
+
require 'rubygems/remote_fetcher'
|
5
|
+
|
6
|
+
module Proscenium
|
7
|
+
class RubyGems
|
8
|
+
def self.path_for(name, version = nil)
|
9
|
+
Pathname new(name, version).path
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name, version = nil)
|
13
|
+
@name = name
|
14
|
+
@version = version
|
15
|
+
end
|
16
|
+
|
17
|
+
def path
|
18
|
+
dependency = Gem::Dependency.new @name, @version
|
19
|
+
path = gem_path dependency
|
20
|
+
|
21
|
+
raise "Gem '#{@name}' not installed nor fetchable." unless path
|
22
|
+
|
23
|
+
basename = File.basename path, '.gem'
|
24
|
+
target_dir = File.expand_path basename, Rails.root.join('tmp', 'unpacked_gems')
|
25
|
+
|
26
|
+
Gem::Package.new(path).extract_files target_dir
|
27
|
+
|
28
|
+
target_dir
|
29
|
+
end
|
30
|
+
|
31
|
+
# Find cached filename in Gem.path. Returns nil if the file cannot be found.
|
32
|
+
def find_in_cache(filename)
|
33
|
+
Gem.path.each do |path|
|
34
|
+
this_path = File.join(path, 'cache', filename)
|
35
|
+
return this_path if File.exist? this_path
|
36
|
+
end
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return the full path to the cached gem file matching the given
|
42
|
+
# name and version requirement. Returns 'nil' if no match.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# get_path 'rake', '> 0.4' # "/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem"
|
47
|
+
# get_path 'rake', '< 0.1' # nil
|
48
|
+
# get_path 'rak' # nil (exact name required)
|
49
|
+
def gem_path(dependency)
|
50
|
+
return dependency.name if /\.gem$/i.match?(dependency.name)
|
51
|
+
|
52
|
+
specs = dependency.matching_specs
|
53
|
+
selected = specs.max_by(&:version)
|
54
|
+
|
55
|
+
return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless selected
|
56
|
+
return unless /^#{selected.name}$/i.match?(dependency.name)
|
57
|
+
|
58
|
+
# We expect to find (basename).gem in the 'cache' directory. Furthermore,
|
59
|
+
# the name match must be exact (ignoring case).
|
60
|
+
path = find_in_cache File.basename selected.cache_file
|
61
|
+
|
62
|
+
return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless path
|
63
|
+
|
64
|
+
path
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -4,7 +4,6 @@ module Proscenium
|
|
4
4
|
class SideLoad
|
5
5
|
JS_COMMENT = '<!-- [PROSCENIUM_JAVASCRIPTS] -->'
|
6
6
|
CSS_COMMENT = '<!-- [PROSCENIUM_STYLESHEETS] -->'
|
7
|
-
LAZY_COMMENT = '<!-- [PROSCENIUM_LAZY_SCRIPTS] -->'
|
8
7
|
|
9
8
|
module Controller
|
10
9
|
def self.included(child)
|
@@ -35,28 +34,16 @@ module Proscenium
|
|
35
34
|
|
36
35
|
return if !fragments && !included_comment
|
37
36
|
|
38
|
-
imports = Proscenium::Importer.imported.dup
|
39
|
-
paths_to_build = []
|
40
|
-
Proscenium::Importer.each_stylesheet(delete: true) do |x, _|
|
41
|
-
paths_to_build << x.delete_prefix('/')
|
42
|
-
end
|
43
|
-
|
44
|
-
result = Proscenium::Builder.build_to_path(paths_to_build.join(';'))
|
45
|
-
|
46
37
|
out = []
|
47
|
-
|
48
|
-
|
49
|
-
inpath.prepend '/'
|
50
|
-
outpath.delete_prefix! 'public'
|
51
|
-
|
52
|
-
next unless imports.key?(inpath)
|
53
|
-
|
54
|
-
import = imports[inpath]
|
55
|
-
opts = import[:css].is_a?(Hash) ? import[:css] : {}
|
38
|
+
Proscenium::Importer.each_stylesheet(delete: true) do |path, opts|
|
39
|
+
opts = opts[:css].is_a?(Hash) ? opts[:css] : {}
|
56
40
|
opts[:preload_links_header] = false if fragments
|
57
41
|
opts[:data] ||= {}
|
58
|
-
|
59
|
-
|
42
|
+
|
43
|
+
if Proscenium.config.cache_query_string.present?
|
44
|
+
path += "?#{Proscenium.config.cache_query_string}"
|
45
|
+
end
|
46
|
+
out << helpers.stylesheet_link_tag(path, extname: false, **opts)
|
60
47
|
end
|
61
48
|
|
62
49
|
if fragments
|
@@ -70,60 +57,30 @@ module Proscenium
|
|
70
57
|
return if response_body.nil?
|
71
58
|
return if response_body.first.blank? || !Proscenium::Importer.js_imported?
|
72
59
|
|
73
|
-
|
74
|
-
paths_to_build = []
|
75
|
-
Proscenium::Importer.each_javascript(delete: true) do |x, _|
|
76
|
-
paths_to_build << x.delete_prefix('/')
|
77
|
-
end
|
78
|
-
|
79
|
-
result = Proscenium::Builder.build_to_path(paths_to_build.join(';'))
|
80
|
-
|
81
|
-
included_js_comment = response_body.first.include?(JS_COMMENT)
|
82
|
-
included_lazy_comment = response_body.first.include?(LAZY_COMMENT)
|
60
|
+
included_comment = response_body.first.include?(JS_COMMENT)
|
83
61
|
fragments = if (fragment_header = request.headers['X-Fragment'])
|
84
62
|
fragment_header.split
|
85
63
|
end
|
86
64
|
|
87
|
-
if fragments
|
88
|
-
out = []
|
89
|
-
scripts = {}
|
90
|
-
result.split(';').each do |x|
|
91
|
-
inpath, outpath = x.split('::')
|
92
|
-
inpath.prepend '/'
|
93
|
-
outpath.delete_prefix! 'public'
|
94
|
-
|
95
|
-
next unless imports.key?(inpath)
|
96
|
-
|
97
|
-
if (import = imports[inpath]).delete(:lazy)
|
98
|
-
scripts[inpath] = import.merge(outpath:)
|
99
|
-
else
|
100
|
-
opts = import[:js].is_a?(Hash) ? import[:js] : {}
|
101
|
-
opts[:preload_links_header] = false if fragments
|
102
|
-
out << helpers.javascript_include_tag(outpath, extname: false, **opts)
|
103
|
-
end
|
104
|
-
end
|
65
|
+
return if !fragments && !included_comment
|
105
66
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
response_body.first.gsub! JS_COMMENT, out.join.html_safe
|
110
|
-
end
|
111
|
-
end
|
67
|
+
out = []
|
68
|
+
Proscenium::Importer.each_javascript(delete: true) do |path, opts|
|
69
|
+
next if opts.delete(:lazy)
|
112
70
|
|
113
|
-
|
71
|
+
opts = opts[:js].is_a?(Hash) ? opts[:js] : {}
|
72
|
+
opts[:preload_links_header] = false if fragments
|
114
73
|
|
115
|
-
|
116
|
-
|
117
|
-
lazy_script = helpers.content_tag 'script', type: 'application/json',
|
118
|
-
id: 'prosceniumLazyScripts' do
|
119
|
-
scripts.to_json.html_safe
|
74
|
+
if Proscenium.config.cache_query_string.present?
|
75
|
+
path += "?#{Proscenium.config.cache_query_string}"
|
120
76
|
end
|
77
|
+
out << helpers.javascript_include_tag(path, extname: false, **opts)
|
121
78
|
end
|
122
79
|
|
123
80
|
if fragments
|
124
|
-
response_body.first.prepend
|
125
|
-
elsif
|
126
|
-
response_body.first.gsub!
|
81
|
+
response_body.first.prepend out.join.html_safe
|
82
|
+
elsif included_comment
|
83
|
+
response_body.first.gsub! JS_COMMENT, out.join.html_safe
|
127
84
|
end
|
128
85
|
end
|
129
86
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"lockfileVersion": 1,
|
3
|
+
"workspaces": {
|
4
|
+
"": {
|
5
|
+
"name": "@proscenium/flash",
|
6
|
+
"dependencies": {
|
7
|
+
"dom-mutations": "^1.0.0",
|
8
|
+
},
|
9
|
+
"devDependencies": {
|
10
|
+
"sourdough-toast": "latest",
|
11
|
+
},
|
12
|
+
},
|
13
|
+
},
|
14
|
+
"packages": {
|
15
|
+
"dom-mutations": ["dom-mutations@1.0.0", "", {}, "sha512-qr4ufk/qu+JKwtz7NPbu6TxpTM/7nqburohI07J+mKSM20USvhcUjEb8hWY6g2a3QYp3LtlGpi+mAZLPuxTi7g=="],
|
16
|
+
|
17
|
+
"sourdough-toast": ["sourdough-toast@0.1.0", "", {}, "sha512-ianhWqaaA5a0n9TRg6dLEt5DWflXqsQUMxEAGZi8JcHGqsPRm7XTWEfhhDKSi/zJ11CBbvh8b8oRcgxjCeYZeg=="],
|
18
|
+
}
|
19
|
+
}
|
@@ -1,5 +1,9 @@
|
|
1
|
-
import domMutations from "
|
2
|
-
import { Sourdough, toast } from "
|
1
|
+
import domMutations from "dom-mutations";
|
2
|
+
import { Sourdough, toast } from "sourdough-toast";
|
3
|
+
|
4
|
+
export function foo() {
|
5
|
+
console.log("foo");
|
6
|
+
}
|
3
7
|
|
4
8
|
class HueFlash extends HTMLElement {
|
5
9
|
static observedAttributes = ["data-flash-alert", "data-flash-notice"];
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export type Options = MutationObserverInit & {signal?: AbortSignal};
|
2
|
+
|
3
|
+
/**
|
4
|
+
@returns An async iterable that yields [`MutationRecord`](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects representing individual mutations.
|
5
|
+
|
6
|
+
@example
|
7
|
+
```
|
8
|
+
import domMutations from 'dom-mutations';
|
9
|
+
|
10
|
+
const target = document.querySelector('#unicorn');
|
11
|
+
|
12
|
+
for await (const mutation of domMutations(target, {childList: true})) {
|
13
|
+
console.log('Mutation:', mutation);
|
14
|
+
}
|
15
|
+
```
|
16
|
+
*/
|
17
|
+
export default function domMutations(target: Node, options?: Options): AsyncIterable<MutationRecord>;
|
18
|
+
|
19
|
+
/**
|
20
|
+
Similar to `domMutations()`, but yields batches of [`MutationRecord`](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects, each batch representing a group of mutations captured together. This method is less convenient, but can be useful in some cases when you need to handle mutations together as a group.
|
21
|
+
|
22
|
+
@example
|
23
|
+
```
|
24
|
+
import {batchedDomMutations} from 'dom-mutations';
|
25
|
+
|
26
|
+
const target = document.querySelector('#unicorn');
|
27
|
+
|
28
|
+
for await (const mutations of batchedDomMutations(target, {childList: true})) {
|
29
|
+
console.log('Batch of mutations:', mutations);
|
30
|
+
}
|
31
|
+
```
|
32
|
+
*/
|
33
|
+
export function batchedDomMutations(target: Node, options?: Options): AsyncIterable<MutationRecord[]>;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
export default function domMutations(target, options = {}) {
|
2
|
+
return {
|
3
|
+
async * [Symbol.asyncIterator]() {
|
4
|
+
for await (const mutations of batchedDomMutations(target, options)) {
|
5
|
+
yield * mutations;
|
6
|
+
}
|
7
|
+
},
|
8
|
+
};
|
9
|
+
}
|
10
|
+
|
11
|
+
export function batchedDomMutations(target, {signal, ...options} = {}) {
|
12
|
+
return {
|
13
|
+
async * [Symbol.asyncIterator]() {
|
14
|
+
signal?.throwIfAborted();
|
15
|
+
|
16
|
+
let resolveMutations;
|
17
|
+
let rejectMutations;
|
18
|
+
|
19
|
+
const observer = new globalThis.MutationObserver(mutations => {
|
20
|
+
resolveMutations?.(mutations);
|
21
|
+
});
|
22
|
+
|
23
|
+
observer.observe(target, options);
|
24
|
+
|
25
|
+
signal?.addEventListener('abort', () => {
|
26
|
+
rejectMutations?.(signal.reason);
|
27
|
+
observer.disconnect();
|
28
|
+
}, {once: true});
|
29
|
+
|
30
|
+
try {
|
31
|
+
while (true) {
|
32
|
+
signal?.throwIfAborted();
|
33
|
+
|
34
|
+
yield await new Promise((resolve, reject) => { // eslint-disable-line no-await-in-loop
|
35
|
+
resolveMutations = resolve;
|
36
|
+
rejectMutations = reject;
|
37
|
+
});
|
38
|
+
}
|
39
|
+
} finally {
|
40
|
+
observer.disconnect();
|
41
|
+
}
|
42
|
+
},
|
43
|
+
};
|
44
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,59 @@
|
|
1
|
+
{
|
2
|
+
"name": "dom-mutations",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Observe changes to the DOM using an async iterable — A nicer API for MutationObserver",
|
5
|
+
"license": "MIT",
|
6
|
+
"repository": "sindresorhus/dom-mutations",
|
7
|
+
"funding": "https://github.com/sponsors/sindresorhus",
|
8
|
+
"author": {
|
9
|
+
"name": "Sindre Sorhus",
|
10
|
+
"email": "sindresorhus@gmail.com",
|
11
|
+
"url": "https://sindresorhus.com"
|
12
|
+
},
|
13
|
+
"type": "module",
|
14
|
+
"exports": {
|
15
|
+
"types": "./index.d.ts",
|
16
|
+
"default": "./index.js"
|
17
|
+
},
|
18
|
+
"sideEffects": false,
|
19
|
+
"engines": {
|
20
|
+
"node": ">=18"
|
21
|
+
},
|
22
|
+
"scripts": {
|
23
|
+
"test": "xo && ava"
|
24
|
+
},
|
25
|
+
"files": [
|
26
|
+
"index.js",
|
27
|
+
"index.d.ts"
|
28
|
+
],
|
29
|
+
"keywords": [
|
30
|
+
"mutationobserver",
|
31
|
+
"mutation",
|
32
|
+
"mutations",
|
33
|
+
"observer",
|
34
|
+
"observe",
|
35
|
+
"dom",
|
36
|
+
"document",
|
37
|
+
"node",
|
38
|
+
"element",
|
39
|
+
"html",
|
40
|
+
"changes",
|
41
|
+
"asynciterable",
|
42
|
+
"asynciterator",
|
43
|
+
"iterable",
|
44
|
+
"iterator",
|
45
|
+
"generator",
|
46
|
+
"async",
|
47
|
+
"events",
|
48
|
+
"stream",
|
49
|
+
"loop"
|
50
|
+
],
|
51
|
+
"devDependencies": {
|
52
|
+
"ava": "^5.3.1",
|
53
|
+
"jsdom": "^24.1.1",
|
54
|
+
"xo": "^0.59.3"
|
55
|
+
},
|
56
|
+
"ava": {
|
57
|
+
"serial": true
|
58
|
+
}
|
59
|
+
}
|