最近在做个工具脚本,作用是类似 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