require 'base64' require 'action_view/helpers' require 'sewing_kit/webpack/manifest' module SewingKit module Webpack module Helper include ActionView::Helpers class UnknownJavaScriptAssetError < StandardError end def sewing_kit_asset_paths(source, extension: 'js') return '' unless source.present? return '/webpack/assets/dll/vendor.js' if Rails.env.development? && 'vendor' == source && extension == 'js' return '' if ['runtime', 'vendor'].include?(source) && !Rails.env.production? paths = SewingKit::Webpack::Manifest.asset_paths(source) return '' unless paths[extension] [paths[extension]].flatten.reject { |path| path =~ /.*\.map$/ } end def sewing_kit_link_tag(*paths) return '' unless Rails.env.production? options = paths.extract_options! tags = paths.uniq.map { |path| create_asset_tag(:link, path, options) } safe_join(tags, "\n") end def sewing_kit_script_tag(*paths) options = paths.extract_options! tags = paths.uniq.map { |path| if path == '' next '' unless Rails.env.production? raise UnknownJavaScriptAssetError.new('Empty JavaSscript path passed to sewing_kit') end create_asset_tag(:script, path, options) } safe_join(tags, "\n") end private def create_asset_tag(tag_type, path, tag_options) raise ArgumentError, "Invalid tag type: #{tag_type}" unless [:script, :link].include? tag_type options = tag_options.clone if tag_options[:integrity] file_hash = extract_hash(path) options[:integrity] = file_hash ? encode_hash(file_hash) : nil end case tag_type when :script content_tag(:script, '', options.reverse_merge(src: path)) when :link tag(:link, options.reverse_merge(href: path, rel: 'stylesheet')) end end def encode_hash(hash) # sewing-kit's default config places a sha256 hash in the filename. Browsers expect integrity # to be the Base64 encoded version of the binary hash prefixed with the hashing algorithm binary_hash = Array(hash).pack('H*') "sha256-#{Base64.strict_encode64(binary_hash)}" end def extract_hash(path) # Consumes sewing-kit's [name]-[chunkhash].[ext] format return unless path =~ /.*-[A-Za-z0-9]{64}\.(js|css)/ path.rpartition('-').last.split('.').first end end end end