proscenium 0.1.0.alpha1-arm64-darwin

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +84 -0
  3. data/README.md +91 -0
  4. data/lib/proscenium/cli/argument_error.js +24 -0
  5. data/lib/proscenium/cli/builders/index.js +1 -0
  6. data/lib/proscenium/cli/builders/javascript.js +45 -0
  7. data/lib/proscenium/cli/builders/react.js +60 -0
  8. data/lib/proscenium/cli/builders/solid.js +46 -0
  9. data/lib/proscenium/cli/esbuild/env_plugin.js +21 -0
  10. data/lib/proscenium/cli/esbuild/resolve_plugin.js +136 -0
  11. data/lib/proscenium/cli/esbuild/solidjs_plugin.js +23 -0
  12. data/lib/proscenium/cli/js_builder.js +194 -0
  13. data/lib/proscenium/cli/solid.js +15 -0
  14. data/lib/proscenium/cli/utils.js +93 -0
  15. data/lib/proscenium/compiler.js +84 -0
  16. data/lib/proscenium/compilers/esbuild/argument_error.js +22 -0
  17. data/lib/proscenium/compilers/esbuild/env_plugin.js +21 -0
  18. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +145 -0
  19. data/lib/proscenium/compilers/esbuild/setup_plugin.js +35 -0
  20. data/lib/proscenium/compilers/esbuild.bench.js +9 -0
  21. data/lib/proscenium/compilers/esbuild.js +82 -0
  22. data/lib/proscenium/css_module.rb +22 -0
  23. data/lib/proscenium/current.rb +9 -0
  24. data/lib/proscenium/helper.rb +32 -0
  25. data/lib/proscenium/link_to_helper.rb +49 -0
  26. data/lib/proscenium/middleware/base.rb +94 -0
  27. data/lib/proscenium/middleware/esbuild.rb +27 -0
  28. data/lib/proscenium/middleware/parcel_css.rb +37 -0
  29. data/lib/proscenium/middleware/runtime.rb +22 -0
  30. data/lib/proscenium/middleware/static.rb +14 -0
  31. data/lib/proscenium/middleware.rb +66 -0
  32. data/lib/proscenium/precompile.rb +31 -0
  33. data/lib/proscenium/railtie.rb +116 -0
  34. data/lib/proscenium/runtime/auto_reload.js +22 -0
  35. data/lib/proscenium/runtime/component_manager/index.js +27 -0
  36. data/lib/proscenium/runtime/component_manager/render_component.js +40 -0
  37. data/lib/proscenium/runtime/import_css.js +46 -0
  38. data/lib/proscenium/runtime/react_shim/index.js +1 -0
  39. data/lib/proscenium/runtime/react_shim/package.json +5 -0
  40. data/lib/proscenium/side_load.rb +96 -0
  41. data/lib/proscenium/version.rb +5 -0
  42. data/lib/proscenium/view_component/tag_builder.rb +23 -0
  43. data/lib/proscenium/view_component.rb +38 -0
  44. data/lib/proscenium.rb +18 -0
  45. data/lib/tasks/assets.rake +19 -0
  46. metadata +179 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proscenium::CssModule
4
+ def initialize(path)
5
+ @path = "#{path}.module.css"
6
+
7
+ return unless Rails.application.config.proscenium.side_load
8
+
9
+ Proscenium::SideLoad.append! Rails.root.join(@path)
10
+ end
11
+
12
+ # Returns an Array of class names generated from the given CSS module `names`.
13
+ def class_names(*names)
14
+ names.flatten.compact.map { |name| "#{name}#{hash}" }
15
+ end
16
+
17
+ private
18
+
19
+ def hash
20
+ @hash ||= Digest::SHA1.hexdigest("/#{@path}")[..7]
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/current_attributes'
4
+
5
+ module Proscenium
6
+ class Current < ActiveSupport::CurrentAttributes
7
+ attribute :loaded
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Helper
5
+ def compute_asset_path(path, options = {})
6
+ return "/#{path}" if %i[javascript stylesheet].include?(options[:type])
7
+
8
+ super
9
+ end
10
+
11
+ def side_load_stylesheets
12
+ return unless Proscenium::Current.loaded
13
+
14
+ stylesheet_link_tag(*Proscenium::Current.loaded[:css])
15
+ end
16
+
17
+ def side_load_javascripts(**options)
18
+ return unless Proscenium::Current.loaded
19
+
20
+ javascript_include_tag(*Proscenium::Current.loaded[:js], options)
21
+ end
22
+
23
+ def proscenium_dev
24
+ return if !Rails.env.development? || !Proscenium::Railtie.websocket
25
+
26
+ javascript_tag %(
27
+ import autoReload from '/proscenium-runtime/auto_reload.js';
28
+ autoReload('#{Proscenium::Railtie.websocket_mount_path}');
29
+ ), type: 'module'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module LinkToHelper
5
+ # Overrides ActionView::Helpers::UrlHelper#link_to to allow passing a component instance as the
6
+ # URL, which will build the URL from the component path, eg. `/components/my_component`. The
7
+ # resulting link tag will also populate the `data` attribute with the component props.
8
+ #
9
+ # Example:
10
+ # link_to 'Go to', MyComponent
11
+ #
12
+ # TODO: ummm, todo it! ;)
13
+ def link_to(*args, &block)
14
+ # name_argument_index = block ? 0 : 1
15
+ # if (args[name_argument_index]).respond_to?(:render_in)
16
+ # return super(*LinkToComponentArguments.new(args, name_argument_index,
17
+ # self).helper_options, &block)
18
+ # end
19
+
20
+ super
21
+ end
22
+ end
23
+
24
+ # Component handling for the `link_to` helper.
25
+ class LinkToComponentArguments
26
+ def initialize(options, name_argument_index, context)
27
+ @options = options
28
+ @name_argument_index = name_argument_index
29
+ @component = @options[@name_argument_index]
30
+
31
+ # We have to render the component, and then extract the props from the component. Rendering
32
+ # first ensures that we have all the correct props.
33
+ context.render @component
34
+ end
35
+
36
+ def helper_options
37
+ @options[@name_argument_index] = "/components#{@component.virtual_path}"
38
+ @options[@name_argument_index += 1] ||= {}
39
+ @options[@name_argument_index][:rel] = 'nofollow'
40
+ @options[@name_argument_index][:data] ||= {}
41
+ @options[@name_argument_index][:data][:component] = {
42
+ path: @component.virtual_path,
43
+ props: @component.props
44
+ }
45
+
46
+ @options
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Proscenium
6
+ class Middleware
7
+ class Base
8
+ include ActiveSupport::Benchmarkable
9
+
10
+ class Error < StandardError; end
11
+
12
+ def self.attempt(request)
13
+ new(request).renderable!&.attempt
14
+ end
15
+
16
+ def initialize(request)
17
+ @request = request
18
+ end
19
+
20
+ def renderable!
21
+ renderable? ? self : nil
22
+ end
23
+
24
+ private
25
+
26
+ def renderable?
27
+ file_readable?
28
+ end
29
+
30
+ def file_readable?(file = @request.path_info)
31
+ return unless (path = clean_path(file))
32
+
33
+ file_stat = File.stat(Pathname(root).join(path.delete_prefix('/').b).to_s)
34
+ rescue SystemCallError
35
+ false
36
+ else
37
+ file_stat.file? && file_stat.readable?
38
+ end
39
+
40
+ def clean_path(file)
41
+ path = Rack::Utils.unescape_path file.chomp('/').delete_prefix('/')
42
+ Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
43
+ end
44
+
45
+ def root
46
+ @root ||= Rails.root.to_s
47
+ end
48
+
49
+ def content_type
50
+ @content_type ||
51
+ ::Rack::Mime.mime_type(::File.extname(@request.path_info), nil) ||
52
+ 'application/javascript'
53
+ end
54
+
55
+ def render_response(content)
56
+ response = Rack::Response.new
57
+ response.write content
58
+ response.content_type = content_type
59
+ response['X-Proscenium-Middleware'] = name
60
+ response.finish
61
+ end
62
+
63
+ def build(cmd)
64
+ stdout, stderr, status = Open3.capture3(cmd)
65
+
66
+ raise Error, stderr unless status.success?
67
+ unless stderr.empty?
68
+ raise "Proscenium build of #{name}:'#{@request.fullpath}' failed -- #{stderr}"
69
+ end
70
+
71
+ stdout
72
+ end
73
+
74
+ def benchmark(type)
75
+ super logging_message(type)
76
+ end
77
+
78
+ # rubocop:disable Style/FormatStringToken
79
+ def logging_message(type)
80
+ format '[Proscenium] Request (%s) %s for %s at %s',
81
+ type, @request.fullpath, @request.ip, Time.now.to_default_s
82
+ end
83
+ # rubocop:enable Style/FormatStringToken
84
+
85
+ def logger
86
+ Rails.logger
87
+ end
88
+
89
+ def name
90
+ @name ||= self.class.name.split('::').last.downcase
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ class Esbuild < Base
6
+ def attempt
7
+ benchmark :esbuild do
8
+ render_response build("#{cli} --root #{root} #{path}")
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def path
15
+ @request.path[1..]
16
+ end
17
+
18
+ def cli
19
+ if ENV['PROSCENIUM_TEST']
20
+ 'deno run -q --import-map import_map.json -A lib/proscenium/compilers/esbuild.js'
21
+ else
22
+ Gem.bin_path 'proscenium', 'esbuild'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module Proscenium
6
+ class Middleware
7
+ class ParcelCss < Base
8
+ def attempt
9
+ benchmark :parcelcss do
10
+ results = build("#{cli} #{cli_options.join ' '} #{root}#{@request.path}")
11
+ render_response css_module? ? Oj.load(results, mode: :strict)['code'] : results
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def cli
18
+ Gem.bin_path 'proscenium', 'parcel_css'
19
+ end
20
+
21
+ def cli_options
22
+ options = ['--nesting', '--custom-media', '--targets', "'>= 0.25%'"]
23
+
24
+ if css_module?
25
+ hash = Digest::SHA1.hexdigest(@request.path)[..7]
26
+ options += ['--css-modules', '--css-modules-pattern', "'[local]#{hash}'"]
27
+ end
28
+
29
+ Rails.env.production? ? options << '-m' : options
30
+ end
31
+
32
+ def css_module?
33
+ @css_module ||= /\.module\.css$/i.match?(@request.path_info)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ class Runtime < Esbuild
6
+ private
7
+
8
+ def renderable?
9
+ old_root = root
10
+ old_path_info = @request.path_info
11
+
12
+ @root = Pathname.new(__dir__).join('../')
13
+ @request.path_info = @request.path_info.sub(%r{^/proscenium-runtime/}, 'runtime/')
14
+
15
+ super
16
+ ensure
17
+ @request.path_info = old_path_info
18
+ @root = old_root
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Middleware
5
+ # Serves static files from disk that end with .js or .css.
6
+ class Static < Base
7
+ def attempt
8
+ benchmark :static do
9
+ Rack::File.new(root, { 'X-Proscenium-Middleware' => 'static' }).call(@request.env)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :Esbuild
9
+ autoload :ParcelCss
10
+ autoload :Runtime
11
+
12
+ MIDDLEWARE_CLASSES = {
13
+ esbuild: Esbuild,
14
+ parcelcss: ParcelCss,
15
+ runtime: Runtime
16
+ }.freeze
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def call(env)
23
+ request = Rack::Request.new(env)
24
+
25
+ return @app.call(env) if !request.get? && !request.head?
26
+
27
+ attempt(request) || @app.call(env)
28
+ end
29
+
30
+ private
31
+
32
+ # Look for the precompiled file in public/assets first, then fallback to the Proscenium
33
+ # middleware that matches the type of file requested, ie: .js => esbuild.
34
+ # See Rails.application.config.proscenium.glob_types.
35
+ def attempt(request)
36
+ return unless (type = find_type(request))
37
+
38
+ file_handler.attempt(request.env) || MIDDLEWARE_CLASSES[type].attempt(request)
39
+ end
40
+
41
+ # Returns the type of file being requested using Rails.application.config.proscenium.glob_types.
42
+ def find_type(request)
43
+ return :runtime if request.path_info.start_with?('/proscenium-runtime/')
44
+
45
+ path = Rails.root.join(request.path[1..])
46
+
47
+ type, = glob_types.find do |_, globs|
48
+ # TODO: Look for the precompiled file in public/assets first
49
+ # globs.any? { |glob| Rails.public_path.join('assets').glob(glob).any?(path) }
50
+
51
+ globs.any? { |glob| Rails.root.glob(glob).any?(path) }
52
+ end
53
+
54
+ type
55
+ end
56
+
57
+ def file_handler
58
+ ::ActionDispatch::FileHandler.new Rails.public_path.join('assets').to_s,
59
+ headers: { 'X-Proscenium-Middleware' => 'precompiled' }
60
+ end
61
+
62
+ def glob_types
63
+ Rails.application.config.proscenium.glob_types
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Proscenium
6
+ class Precompile
7
+ def self.call
8
+ new.call
9
+ end
10
+
11
+ def call
12
+ Rails.application.config.proscenium.glob_types.find do |type, globs|
13
+ cmd = "#{cli type} --root #{Rails.root} '#{globs.join "' '"}' --write"
14
+ _, stderr, status = Open3.capture3(cmd)
15
+
16
+ raise stderr unless status.success?
17
+ raise "#{type} compiliation failed -- #{stderr}" unless stderr.empty?
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def cli(type)
24
+ if ENV['PROSCENIUM_TEST']
25
+ "deno run -q --import-map import_map.json -A lib/proscenium/compilers/#{type}.js"
26
+ else
27
+ Gem.bin_path 'proscenium', type.to_s
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+ require 'action_cable/engine'
5
+ require 'listen'
6
+
7
+ ENV['RAILS_ENV'] = Rails.env
8
+
9
+ module Proscenium
10
+ # These globs should actually be Deno supported globs, and not ruby globs. This is because when
11
+ # precompiling, the glob paths are passed as is to the compiler run by Deno.
12
+ #
13
+ # See https://doc.deno.land/https://deno.land/std@0.145.0/path/mod.ts/~/globToRegExp
14
+ DEFAULT_GLOB_TYPES = {
15
+ esbuild: [
16
+ 'lib/**/*.{js,jsx}',
17
+ 'app/components/**/*.{js,jsx}',
18
+ 'app/views/**/*.{js,jsx}'
19
+ ],
20
+ parcelcss: [
21
+ 'lib/**/*.css',
22
+ 'app/components/**/*.css',
23
+ 'app/views/**/*.css'
24
+ ]
25
+ }.freeze
26
+
27
+ class << self
28
+ def config
29
+ @config ||= Railtie.config.proscenium
30
+ end
31
+ end
32
+
33
+ class Railtie < ::Rails::Engine
34
+ isolate_namespace Proscenium
35
+
36
+ config.proscenium = ActiveSupport::OrderedOptions.new
37
+ config.proscenium.listen_paths ||= %w[lib app]
38
+ config.proscenium.listen_extensions ||= /\.(css|jsx?)$/
39
+ config.proscenium.side_load = true
40
+
41
+ initializer 'proscenium.configuration' do |app|
42
+ options = app.config.proscenium
43
+
44
+ options.glob_types = DEFAULT_GLOB_TYPES if options.glob_types.blank?
45
+
46
+ options.auto_refresh = true if options.auto_refresh.nil?
47
+ options.listen = Rails.env.development? if options.listen.nil?
48
+ options.listen_paths.filter! { |path| Dir.exist? path }
49
+ options.cable_mount_path ||= '/proscenium-cable'
50
+ options.cable_logger ||= Rails.logger
51
+ end
52
+
53
+ initializer 'proscenium.side_load' do |_app|
54
+ Proscenium::Current.loaded ||= SideLoad::EXTENSIONS.to_h { |e| [e, Set[]] }
55
+ end
56
+
57
+ initializer 'proscenium.middleware' do |app|
58
+ app.middleware.insert_after ActionDispatch::Static, Proscenium::Middleware
59
+ app.middleware.insert_after ActionDispatch::Static, Rack::ETag, 'no-cache'
60
+ app.middleware.insert_after ActionDispatch::Static, Rack::ConditionalGet
61
+ end
62
+
63
+ initializer 'proscenium.helpers' do |_app|
64
+ ActiveSupport.on_load(:action_view) do
65
+ ActionView::Base.include Proscenium::Helper
66
+
67
+ if Rails.application.config.proscenium.side_load
68
+ ActionView::TemplateRenderer.prepend SideLoad::Monkey::TemplateRenderer
69
+ end
70
+
71
+ ActionView::Helpers::UrlHelper.prepend Proscenium::LinkToHelper
72
+ end
73
+ end
74
+
75
+ config.after_initialize do
76
+ next unless config.proscenium.listen
77
+
78
+ @listener = Listen.to(*config.proscenium.listen_paths,
79
+ only: config.proscenium.listen_extensions) do |mod, add, rem|
80
+ Proscenium::Railtie.websocket&.broadcast('reload', {
81
+ modified: mod,
82
+ removed: rem,
83
+ added: add
84
+ })
85
+ end
86
+
87
+ @listener.start
88
+ end
89
+
90
+ at_exit do
91
+ @listener&.stop
92
+ end
93
+
94
+ class << self
95
+ def websocket
96
+ return unless config.proscenium.auto_refresh
97
+
98
+ cable = ActionCable::Server::Configuration.new
99
+ cable.cable = { adapter: 'async' }.with_indifferent_access
100
+ cable.mount_path = config.proscenium.cable_mount_path
101
+ cable.connection_class = -> { Proscenium::Connection }
102
+ cable.logger = config.proscenium.cable_logger
103
+
104
+ @websocket ||= ActionCable::Server::Base.new(config: cable)
105
+ end
106
+
107
+ def websocket_mount_path
108
+ "#{mounted_path}#{config.proscenium.cable_mount_path}" if websocket
109
+ end
110
+
111
+ def mounted_path
112
+ Proscenium::Railtie.routes.find_script_name({})
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,22 @@
1
+ import { createConsumer } from 'https://esm.sh/@rails/actioncable@6.0.5'
2
+ import debounce from 'https://esm.sh/debounce@1.2.1'
3
+
4
+ export default socketPath => {
5
+ const uid = (Date.now() + ((Math.random() * 100) | 0)).toString()
6
+ const consumer = createConsumer(`${socketPath}?uid=${uid}`)
7
+
8
+ consumer.subscriptions.create('Proscenium::ReloadChannel', {
9
+ received: debounce(() => {
10
+ console.log('Proscenium files changed; reloading...')
11
+ location.reload()
12
+ }, 200),
13
+
14
+ connected() {
15
+ console.log('Proscenium auto reload websocket connected')
16
+ },
17
+
18
+ disconnected() {
19
+ console.log('Proscenium auto reload websocket disconnected')
20
+ }
21
+ })
22
+ }
@@ -0,0 +1,27 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import renderComponent from `/proscenium-runtime/component_manager/render_component.js`
4
+
5
+ document.addEventListener('DOMContentLoaded', () => {
6
+ const elements = document.querySelectorAll('[data-component]')
7
+
8
+ if (elements.length < 1) return
9
+
10
+ Array.from(elements, (ele) => {
11
+ const data = JSON.parse(ele.getAttribute('data-component'))
12
+
13
+ let isVisible = false
14
+ const observer = new IntersectionObserver((entries) => {
15
+ entries.forEach((entry) => {
16
+ if (!isVisible && entry.isIntersecting) {
17
+ isVisible = true
18
+ observer.unobserve(ele)
19
+
20
+ renderComponent(ele, data)
21
+ }
22
+ })
23
+ })
24
+
25
+ observer.observe(ele)
26
+ })
27
+ })
@@ -0,0 +1,40 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { RAILS_ENV } from 'env'
4
+
5
+ // We don't use JSX, as doing so would auto-inject React. We don't want to do this, as React is lazy
6
+ // loaded only when needed.
7
+ export default async function (ele, data) {
8
+ const { createElement, useEffect, lazy, Suspense } = await import('react')
9
+ const { createRoot } = await import('react-dom/client')
10
+
11
+ const component = lazy(() => import(`/app/components${data.path}.jsx`))
12
+ const contentLoader = data.contentLoader && ele.firstElementChild
13
+
14
+ const Fallback = ({ contentLoader }) => {
15
+ useEffect(() => {
16
+ contentLoader && contentLoader.remove()
17
+ }, []) // eslint-disable-line react-hooks/exhaustive-deps
18
+
19
+ if (!contentLoader) return null
20
+
21
+ return /* @__PURE__ */ createElement('div', {
22
+ style: { height: '100%' },
23
+ dangerouslySetInnerHTML: { __html: contentLoader.outerHTML }
24
+ })
25
+ }
26
+
27
+ createRoot(ele).render(
28
+ /* @__PURE__ */ createElement(
29
+ Suspense,
30
+ {
31
+ fallback: /* @__PURE__ */ createElement(Fallback, {
32
+ contentLoader
33
+ })
34
+ },
35
+ createElement(component, data.props)
36
+ )
37
+ )
38
+
39
+ RAILS_ENV === 'development' && console.debug(`[REACT]`, `Rendered ${data.path.slice(1)}`)
40
+ }
@@ -0,0 +1,46 @@
1
+ async function digest(value) {
2
+ value = new TextEncoder().encode(value)
3
+ const view = new DataView(await crypto.subtle.digest('SHA-1', value))
4
+
5
+ let hexCodes = ''
6
+ for (let index = 0; index < view.byteLength; index += 4) {
7
+ hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
8
+ }
9
+
10
+ return hexCodes.slice(0, 8)
11
+ }
12
+
13
+ const proxyCache = {}
14
+
15
+ export async function importCssModule(path) {
16
+ appendStylesheet(path)
17
+
18
+ if (Object.keys(proxyCache).includes(path)) {
19
+ return proxyCache[path]
20
+ }
21
+
22
+ const hashValue = await digest(path)
23
+ return (proxyCache[path] = new Proxy(
24
+ {},
25
+ {
26
+ get(target, prop, receiver) {
27
+ if (prop in target || typeof prop === 'symbol') {
28
+ return Reflect.get(target, prop, receiver)
29
+ } else {
30
+ return `${prop}${hashValue}`
31
+ }
32
+ }
33
+ }
34
+ ))
35
+ }
36
+
37
+ export function appendStylesheet(path) {
38
+ // Make sure we only load the stylesheet once.
39
+ if (document.head.querySelector(`link[rel=stylesheet][href='${path}']`)) return
40
+
41
+ const ele = document.createElement('link')
42
+ ele.setAttribute('rel', 'stylesheet')
43
+ ele.setAttribute('media', 'all')
44
+ ele.setAttribute('href', path)
45
+ document.head.appendChild(ele)
46
+ }
@@ -0,0 +1 @@
1
+ export { createElement as reactCreateElement, Fragment as ReactFragment } from 'react'
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "react_shim",
3
+ "private": true,
4
+ "sideEffects": false
5
+ }