proscenium 0.1.0.alpha1-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
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
+ }