proscenium 0.7.0-aarch64-linux

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.
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'oj'
5
+
6
+ module Proscenium
7
+ class Esbuild::Golib
8
+ class Result < FFI::Struct
9
+ layout :success, :bool,
10
+ :response, :string
11
+ end
12
+
13
+ module Request
14
+ extend FFI::Library
15
+ ffi_lib Pathname.new(__dir__).join('../ext/proscenium').to_s
16
+
17
+ enum :environment, [:development, 1, :test, :production]
18
+
19
+ attach_function :build, [
20
+ :string, # path or entry point
21
+ :string, # root
22
+ :string, # base URL of the Rails app. eg. https://example.com
23
+ :environment, # Rails environment as a Symbol
24
+ :string, # path to import map, relative to root
25
+ :bool # debugging enabled?
26
+ ], Result.by_value
27
+
28
+ attach_function :resolve, [
29
+ :string, # path or entry point
30
+ :string, # root
31
+ :environment, # Rails environment as a Symbol
32
+ :string # path to import map, relative to root
33
+ ], Result.by_value
34
+ end
35
+
36
+ class BuildError < StandardError
37
+ attr_reader :error, :path
38
+
39
+ def initialize(path, error)
40
+ error = Oj.load(error, mode: :strict).deep_transform_keys(&:underscore)
41
+
42
+ super "Failed to build '#{path}' -- #{error['text']}"
43
+ end
44
+ end
45
+
46
+ class ResolveError < StandardError
47
+ attr_reader :error_msg, :path
48
+
49
+ def initialize(path, error_msg)
50
+ super "Failed to resolve '#{path}' -- #{error_msg}"
51
+ end
52
+ end
53
+
54
+ def initialize(root: nil, base_url: nil)
55
+ @root = root || Rails.root
56
+ @base_url = base_url
57
+ end
58
+
59
+ def self.resolve(path)
60
+ new.resolve(path)
61
+ end
62
+
63
+ def self.build(path)
64
+ new.build(path)
65
+ end
66
+
67
+ def build(path)
68
+ result = Request.build(path, @root.to_s, @base_url, Rails.env.to_sym, import_map,
69
+ Rails.env.development?)
70
+ raise BuildError.new(path, result[:response]) unless result[:success]
71
+
72
+ result[:response]
73
+ end
74
+
75
+ def resolve(path)
76
+ result = Request.resolve(path, @root.to_s, Rails.env.to_sym, import_map)
77
+ raise ResolveError.new(path, result[:response]) unless result[:success]
78
+
79
+ result[:response]
80
+ end
81
+
82
+ private
83
+
84
+ def import_map
85
+ return unless (path = Rails.root&.join('config'))
86
+
87
+ if (json = path.join('import_map.json')).exist?
88
+ return json.relative_path_from(@root).to_s
89
+ end
90
+ if (js = path.join('import_map.js')).exist?
91
+ return js.relative_path_from(@root).to_s
92
+ end
93
+
94
+ nil
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Esbuild
5
+ class CompileError < StandardError; end
6
+
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :Golib
10
+
11
+ def self.build(...)
12
+ new(...).build
13
+ end
14
+
15
+ def initialize(path, root:, base_url:)
16
+ @path = path
17
+ @root = root
18
+ @base_url = base_url
19
+ end
20
+
21
+ def build
22
+ Proscenium::Esbuild::Golib.new(root: @root, base_url: @base_url).build(@path)
23
+ end
24
+
25
+ private
26
+
27
+ def cache_query_string
28
+ q = Proscenium.config.cache_query_string
29
+ q ? "--cache-query-string #{q}" : nil
30
+ end
31
+ end
32
+ end
Binary file
@@ -0,0 +1,109 @@
1
+ /* Code generated by cmd/cgo; DO NOT EDIT. */
2
+
3
+ /* package joelmoss/proscenium */
4
+
5
+
6
+ #line 1 "cgo-builtin-export-prolog"
7
+
8
+ #include <stddef.h>
9
+
10
+ #ifndef GO_CGO_EXPORT_PROLOGUE_H
11
+ #define GO_CGO_EXPORT_PROLOGUE_H
12
+
13
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
14
+ typedef struct { const char *p; ptrdiff_t n; } _GoString_;
15
+ #endif
16
+
17
+ #endif
18
+
19
+ /* Start of preamble from import "C" comments. */
20
+
21
+
22
+ #line 3 "main.go"
23
+
24
+ struct Result {
25
+ int success;
26
+ char* response;
27
+ };
28
+
29
+ #line 1 "cgo-generated-wrapper"
30
+
31
+
32
+ /* End of preamble from import "C" comments. */
33
+
34
+
35
+ /* Start of boilerplate cgo prologue. */
36
+ #line 1 "cgo-gcc-export-header-prolog"
37
+
38
+ #ifndef GO_CGO_PROLOGUE_H
39
+ #define GO_CGO_PROLOGUE_H
40
+
41
+ typedef signed char GoInt8;
42
+ typedef unsigned char GoUint8;
43
+ typedef short GoInt16;
44
+ typedef unsigned short GoUint16;
45
+ typedef int GoInt32;
46
+ typedef unsigned int GoUint32;
47
+ typedef long long GoInt64;
48
+ typedef unsigned long long GoUint64;
49
+ typedef GoInt64 GoInt;
50
+ typedef GoUint64 GoUint;
51
+ typedef size_t GoUintptr;
52
+ typedef float GoFloat32;
53
+ typedef double GoFloat64;
54
+ #ifdef _MSC_VER
55
+ #include <complex.h>
56
+ typedef _Fcomplex GoComplex64;
57
+ typedef _Dcomplex GoComplex128;
58
+ #else
59
+ typedef float _Complex GoComplex64;
60
+ typedef double _Complex GoComplex128;
61
+ #endif
62
+
63
+ /*
64
+ static assertion to make sure the file is being used on architecture
65
+ at least with matching size of GoInt.
66
+ */
67
+ typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
68
+
69
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
70
+ typedef _GoString_ GoString;
71
+ #endif
72
+ typedef void *GoMap;
73
+ typedef void *GoChan;
74
+ typedef struct { void *t; void *v; } GoInterface;
75
+ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
76
+
77
+ #endif
78
+
79
+ /* End of boilerplate cgo prologue. */
80
+
81
+ #ifdef __cplusplus
82
+ extern "C" {
83
+ #endif
84
+
85
+
86
+ // Build the given `path` in the `root`.
87
+ //
88
+ // - path - The path to build relative to `root`.
89
+ // - root - The working directory.
90
+ // - baseUrl - base URL of the Rails app. eg. https://example.com
91
+ // - env - The environment (1 = development, 2 = test, 3 = production)
92
+ // - importMap - Path to the import map relative to `root`.
93
+ // - bundle
94
+ // - debug
95
+ //
96
+ extern struct Result build(char* filepath, char* root, char* baseUrl, unsigned int env, char* importMap, GoUint8 bundle, GoUint8 debug);
97
+
98
+ // Resolve the given `path` relative to the `root`.
99
+ //
100
+ // - path - The path to build relative to `root`.
101
+ // - root - The working directory.
102
+ // - env - The environment (1 = development, 2 = test, 3 = production)
103
+ // - importMap - Path to the import map relative to `root`.
104
+ //
105
+ extern struct Result resolve(char* path, char* root, unsigned int env, char* importMap);
106
+
107
+ #ifdef __cplusplus
108
+ }
109
+ #endif
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ module Helper
5
+ def compute_asset_path(path, options = {})
6
+ if %i[javascript stylesheet].include?(options[:type])
7
+ result = "/#{path}"
8
+
9
+ if (qs = Proscenium.config.cache_query_string)
10
+ result << "?#{qs}"
11
+ end
12
+
13
+ return result
14
+ end
15
+
16
+ super
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module Proscenium
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+ def sideload(event)
8
+ info do
9
+ " [Proscenium] Side loaded #{event.payload[:identifier]}"
10
+ end
11
+ end
12
+
13
+ def build(event)
14
+ path = event.payload[:identifier]
15
+ path = path.start_with?(/https?%3A%2F%2F/) ? CGI.unescape(path) : path
16
+
17
+ info do
18
+ message = +"[Proscenium] Building #{path}"
19
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
20
+ message << "\n" if defined?(Rails.env) && Rails.env.development?
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Proscenium::LogSubscriber.attach_to :proscenium
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'oj'
5
+
6
+ module Proscenium
7
+ class Middleware
8
+ class Base
9
+ include ActiveSupport::Benchmarkable
10
+
11
+ # Error when the result of the build returns an error. For example, when esbuild returns
12
+ # errors.
13
+ class CompileError < StandardError
14
+ attr_reader :detail, :file
15
+
16
+ def initialize(args)
17
+ @detail = args[:detail]
18
+ @file = args[:file]
19
+ super "Failed to build '#{args[:file]}' -- #{detail}"
20
+ end
21
+ end
22
+
23
+ def self.attempt(request)
24
+ new(request).renderable!&.attempt
25
+ end
26
+
27
+ def initialize(request)
28
+ @request = request
29
+ end
30
+
31
+ def renderable!
32
+ renderable? ? self : nil
33
+ end
34
+
35
+ private
36
+
37
+ def real_path
38
+ @request.path
39
+ end
40
+
41
+ # @return [String] the path to the file without the leading slash which will be built.
42
+ def path_to_build
43
+ @request.path[1..]
44
+ end
45
+
46
+ def sourcemap?
47
+ @request.path.ends_with?('.map')
48
+ end
49
+
50
+ def renderable?
51
+ file_readable?
52
+ end
53
+
54
+ def file_readable?
55
+ return unless (path = clean_path(sourcemap? ? real_path[0...-4] : real_path))
56
+
57
+ file_stat = File.stat(Pathname(root).join(path.delete_prefix('/').b).to_s)
58
+ rescue SystemCallError
59
+ false
60
+ else
61
+ file_stat.file? && file_stat.readable?
62
+ end
63
+
64
+ def clean_path(file)
65
+ path = Rack::Utils.unescape_path file.chomp('/').delete_prefix('/')
66
+ Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
67
+ end
68
+
69
+ def root
70
+ @root ||= Rails.root.to_s
71
+ end
72
+
73
+ def content_type
74
+ @content_type ||
75
+ ::Rack::Mime.mime_type(::File.extname(path_to_build), nil) ||
76
+ 'application/javascript'
77
+ end
78
+
79
+ def render_response(content)
80
+ response = Rack::Response.new
81
+ response.write content
82
+ response.content_type = content_type
83
+ response['X-Proscenium-Middleware'] = name
84
+ response.set_header 'SourceMap', "#{@request.path_info}.map"
85
+
86
+ if Proscenium.config.cache_query_string && Proscenium.config.cache_max_age
87
+ response.cache! Proscenium.config.cache_max_age
88
+ end
89
+
90
+ yield response if block_given?
91
+
92
+ response.finish
93
+ end
94
+
95
+ def name
96
+ @name ||= self.class.name.split('::').last.downcase
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ class Esbuild < Base
6
+ class CompileError < Base::CompileError
7
+ def initialize(args)
8
+ detail = args[:detail]
9
+ detail = ActiveSupport::HashWithIndifferentAccess.new(Oj.load(detail, mode: :strict))
10
+
11
+ args[:detail] = if detail[:location]
12
+ "#{detail[:text]} in #{detail[:location][:file]}:" +
13
+ detail[:location][:line].to_s
14
+ else
15
+ detail[:text]
16
+ end
17
+
18
+ super args
19
+ end
20
+ end
21
+
22
+ def attempt
23
+ ActiveSupport::Notifications.instrument('build.proscenium', identifier: path_to_build) do
24
+ render_response Proscenium::Esbuild.build(path_to_build, root: root,
25
+ base_url: @request.base_url)
26
+ end
27
+ rescue Proscenium::Esbuild::CompileError => e
28
+ raise self.class::CompileError, { file: @request.fullpath, detail: e.message }, caller
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ # Handles requests for URL encoded URL's.
6
+ class Url < Esbuild
7
+ private
8
+
9
+ # @override [Esbuild] It's a URL, so always assume it is renderable (we won't actually know
10
+ # until it's downloaded).
11
+ def renderable?
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium
4
+ class Middleware
5
+ extend ActiveSupport::Autoload
6
+
7
+ # Error when the build command fails.
8
+ class BuildError < StandardError; end
9
+
10
+ autoload :Base
11
+ autoload :Esbuild
12
+ autoload :Url
13
+
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ def call(env)
19
+ request = Rack::Request.new(env)
20
+
21
+ return @app.call(env) if !request.get? && !request.head?
22
+
23
+ attempt(request) || @app.call(env)
24
+ end
25
+
26
+ private
27
+
28
+ def attempt(request)
29
+ return unless (type = find_type(request))
30
+
31
+ # file_handler.attempt(request.env) || type.attempt(request)
32
+
33
+ type.attempt(request)
34
+ end
35
+
36
+ # Returns the type of file being requested using Proscenium::MIDDLEWARE_GLOB_TYPES.
37
+ def find_type(request)
38
+ path = Pathname.new(request.path)
39
+
40
+ return Url if request.path.match?(glob_types[:url])
41
+ return Esbuild if path.fnmatch?(application_glob_type, File::FNM_EXTGLOB)
42
+ end
43
+
44
+ # TODO: handle precompiled assets
45
+ def file_handler
46
+ ::ActionDispatch::FileHandler.new Rails.public_path.join('assets').to_s,
47
+ headers: { 'X-Proscenium-Middleware' => 'precompiled' }
48
+ end
49
+
50
+ def glob_types
51
+ @glob_types ||= Proscenium::MIDDLEWARE_GLOB_TYPES
52
+ end
53
+
54
+ def application_glob_type
55
+ paths = Rails.application.config.proscenium.include_paths.join(',')
56
+ "/{#{paths}}#{glob_types[:application]}"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proscenium::Phlex::ComponentConcerns
4
+ module CssModules
5
+ extend ActiveSupport::Concern
6
+ include Proscenium::CssModule
7
+ include Proscenium::Phlex::ResolveCssModules
8
+
9
+ # class_methods do
10
+ # # FIXME: Still needed?
11
+ # def path
12
+ # pp name, super
13
+ # pp Module.const_source_location(name).first
14
+
15
+ # name && Pathname.new(Module.const_source_location(name).first)
16
+ # rescue NameError
17
+ # nil
18
+ # end
19
+ # end
20
+
21
+ private
22
+
23
+ def path
24
+ self.class.path
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'phlex/rails'
4
+
5
+ # Include this in your view for additional logic for rendering a full HTML page, usually from a
6
+ # controller.
7
+ module Proscenium::Phlex::Page
8
+ include Phlex::Rails::Helpers::CSPMetaTag
9
+ include Phlex::Rails::Helpers::CSRFMetaTags
10
+ include Phlex::Rails::Helpers::FaviconLinkTag
11
+ include Phlex::Rails::Helpers::PreloadLinkTag
12
+ include Phlex::Rails::Helpers::StyleSheetLinkTag
13
+ include Phlex::Rails::Helpers::ActionCableMetaTag
14
+ include Phlex::Rails::Helpers::AutoDiscoveryLinkTag
15
+ include Phlex::Rails::Helpers::JavaScriptIncludeTag
16
+ include Phlex::Rails::Helpers::JavaScriptImportMapTags
17
+ include Phlex::Rails::Helpers::JavaScriptImportModuleTag
18
+
19
+ def self.included(klass)
20
+ klass.extend(Phlex::Rails::Layout::Interface)
21
+ end
22
+
23
+ def template(&block)
24
+ doctype
25
+ html do
26
+ head
27
+ body(&block)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def after_template
34
+ super
35
+ @_buffer.gsub!('<!-- [SIDE_LOAD_STYLESHEETS] -->', capture { side_load_stylesheets })
36
+ end
37
+
38
+ def page_title
39
+ Rails.application.class.name.deconstantize
40
+ end
41
+
42
+ def head
43
+ super do
44
+ title { page_title }
45
+
46
+ yield if block_given?
47
+
48
+ csp_meta_tag
49
+ csrf_meta_tags
50
+
51
+ comment { '[SIDE_LOAD_STYLESHEETS]' }
52
+ end
53
+ end
54
+
55
+ def body
56
+ super do
57
+ yield if block_given?
58
+
59
+ side_load_javascripts defer: true, type: :module
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Renders a div for use with @proscenium/component-manager.
5
+ #
6
+ # You can pass props to the component in the `:props` keyword argument.
7
+ #
8
+ # By default, the component is lazy loaded when intersecting using IntersectionObserver. Pass in
9
+ # :lazy as false to disable this and render the component immediately.
10
+ #
11
+ # React components are not side loaded at all.
12
+ #
13
+ class Proscenium::Phlex::ReactComponent < Phlex::HTML
14
+ class << self
15
+ attr_accessor :path, :abstract_class
16
+
17
+ def inherited(child)
18
+ position = caller_locations(1, 1).first.label == 'inherited' ? 2 : 1
19
+ child.path = Pathname.new caller_locations(position, 1).first.path.sub(/\.rb$/, '')
20
+
21
+ super
22
+ end
23
+ end
24
+
25
+ self.abstract_class = true
26
+
27
+ include Proscenium::Phlex::ComponentConcerns::CssModules
28
+
29
+ attr_writer :props, :lazy
30
+
31
+ # @param props: [Hash]
32
+ # @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
33
+ def initialize(props: {}, lazy: true) # rubocop:disable Lint/MissingSuper
34
+ @props = props
35
+ @lazy = lazy
36
+ end
37
+
38
+ # @yield the given block to a `div` within the top level component div. If not given,
39
+ # `<div>loading...</div>` will be rendered. Use this to display a loading UI while the component
40
+ # is loading and rendered.
41
+ def template(**attributes, &block)
42
+ component_root(:div, **attributes, &block)
43
+ end
44
+
45
+ private
46
+
47
+ def component_root(element, **attributes, &block)
48
+ send element, data: { proscenium_component: component_data }, **attributes, &block
49
+ end
50
+
51
+ def props
52
+ @props ||= {}
53
+ end
54
+
55
+ def lazy
56
+ instance_variable_defined?(:@lazy) ? @lazy : (@lazy = false)
57
+ end
58
+
59
+ def component_data
60
+ {
61
+ path: virtual_path, lazy: lazy,
62
+ props: props.deep_transform_keys { |k| k.to_s.camelize :lower }
63
+ }.to_json
64
+ end
65
+
66
+ def virtual_path
67
+ path.to_s.delete_prefix(Rails.root.to_s)
68
+ end
69
+ end