proscenium 0.7.0-aarch64-linux

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