Ruby 将 Ruby 项目的文件打包到单文件内,如何解决交叉依赖?

molingyu · 2018年09月11日 · 最后由 huacnlee 回复于 2018年09月12日 · 1754 次阅读

最近在做个工具脚本,作用是类似 js 的那些打包工具,可以把项目下的文件依据 require 顺序打包到一个文件内。 最开始项目没有依赖外部的 gem 包。所以只支持了 require_relative。交叉依赖的问题手动标记来消除。

require 'fileutils'
require 'zlib'

module RbPack
  class RbPack
    class << self

      # @return [Array<Proc>]
      attr_reader :loaders

      def use(loader)
        @loaders = [] unless @loaders
        @loaders << loader
      end

    end

    attr_reader :excludes
    attr_reader :source
    attr_reader :output

    def initialize
      @visited = {}
      @cont = []
      @count = 0
      @files = {}
    end

    def get_requires
      until @cont.empty?
        visit(@cont.shift, @visited, @cont)
      end
    end

    def get_file_name(file)
      "\n\n# File #{file.gsub(Dir::getwd + '/', '')}\n\n"
    end

    def get_file(file)
      @files[file]
          .gsub("# encoding:utf-8\n", '')
          .strip
    end

    def init_loaders
      RbPack.loaders.map! { |loader| loader.new(self) }
    end

    def create_file
      FileUtils.mkdir_p(File.dirname(@output))
      File.open(@output, 'w')
    end

    def pack(conf)
      @source = File.expand_path(conf[:source], File.dirname(__FILE__))
      @excludes = conf[:excludes] || []
      @output = conf[:output] || './out.rb'
      @excludes.map!{|exclude| File.expand_path(exclude, File.dirname(@source)) }
      init_loaders
      @cont << @source
      get_requires
      list = reorder(@source, @visited[@source])
      puts "output: #{@output}"
      out_rb(list)
      puts "Finish! #{@count} files."
    end

    def out_rb(list)
      create_file.write ("# encoding:utf-8\n" + list.map { |file|
        file_str = get_file(file)
        next '' if file_str == ''
        @count += 1
        puts file
        get_file_name(file) + file_str
      }.reduce(:concat).strip)
    end

    # @param [String] file
    # @param [Hash] visited
    # @param [Array] cont
    def visit(file, visited, cont)
      return if visited.include? file
      visited[file] = []
      File.open(file) do |f|
        @files[file] = f.readlines.map { |str|
          delete = false
          RbPack.loaders.each do |loader|
            if loader.match(str, f)
              depends = loader.file
              next delete = true if depends == file
              cont << depends unless cont.include? depends
              visited[file] << depends
            end
            delete =  delete || loader.delete
          end
          delete ? '' : str
        }.reduce(:concat)
      end
    end

    # @param [String] file
    # @param [Array<String>] dependency
    def reorder(file, dependency)
      return [file] if dependency == [] || dependency == nil
      dependency.map {|dep| reorder(dep, @visited[dep]) }.push(file).flatten.uniq
    end
  end

  class RequireRelativeLoader

    attr_reader :file
    attr_reader :delete

    def initialize(conf)
      @excludes = conf.excludes
      @file = ''
      @match_exp = /require_relative[^\n]*/
      @file_exp = /require_relative\s*'([^']*)'/
      @mark_exp = /require_relative\s*'[^']*'\s*\#delete\s/
      @delete = true
    end

    def add_suffix(str)
      return str + '.rb' unless str.end_with? 'rb'
      str
    end

    def match(str, f)
      @delete = false
      return false if str !~ @match_exp
      @delete = true
      return false if str =~ @mark_exp
      return false if str !~ @file_exp
      @file = File.expand_path(add_suffix($1), File.dirname(f))
      return false if @excludes.include? @file
      @file
    end

  end

  RbPack.use(RequireRelativeLoader)

end

def rb_pack
  RbPack::RbPack.new
end

最近依赖了一个外部的 gem,所以打算支持一下 require。然后就遇到了交叉依赖的问题。尝试直接用 $LOADED_FEATURES 里的载入顺序,结果发现按这个顺序打包出来的单文件依然会报错

不知道有没有现成的轮子,或者这个问题有什么解决办法没。ORZ

我觉得要打包 用 jruby/warbler 来打 war 更好 🐶

Docker 打包,用 Docker 来执行

需要 登录 后方可回复, 如果你还没有账号请 注册新账号