Skip to content

Instantly share code, notes, and snippets.

@cheery
Created January 29, 2014 19:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cheery/8695443 to your computer and use it in GitHub Desktop.
Save cheery/8695443 to your computer and use it in GitHub Desktop.
How would I get source maps working here? The scripts are loaded from inside an archive, they need to be wrapped into modules so they find each other.
zip.workerScriptsPath = "lib/"
$ ->
archiveURL = 'app.zip'
requestBlob archiveURL, {
load: (blob) ->
new ZipReader blob, {
load: (entries) ->
console.log "loaded"
runModule(archiveURL, entries, {}, 'scripts/main')
progress: (count) ->
console.log "#{count} progress"
}
}
# In the combination with "watch" -script,
# this thing reloads the document whenever package changes.
# it's for debugging purposes and is triggered only
# in localhost domain.
if document.domain == 'localhost'
last_modified = 0
debug_beat = () ->
get_last_modified archiveURL, (t) ->
if t != last_modified
document.location.reload()
get_last_modified archiveURL, (t) ->
last_modified = t
setInterval debug_beat, 2000
get_last_modified = (url, callback) ->
xhr = new XMLHttpRequest()
xhr.open("HEAD", url, true)
xhr.onreadystatechange = () ->
if xhr.readyState == 4 and xhr.status != 304
callback(xhr.getResponseHeader('Last-Modified'))
xhr.send()
runModule = (archiveURL, entries, modules, path) ->
module = modules[path]
return module.exports if module?
module = modules[path] = {}
module.loaded = false
module.path = parsePath(path)
module.exports = exports = {}
require = (name) ->
reqPath = module.path.pop().concat(parsePath(name)).toString(false)
return runModule(archiveURL, entries, modules, reqPath)
get = (name) ->
reqPath = module.path.pop().concat(parsePath(name)).toString(false)
return entries[reqPath]
filename = path + '.coffee'
source = entries[filename]
if archiveURL?
sourceURL = archiveURL + '/' + filename
else
sourceURL = filename
# coffeescript & sourcemaps have problems in this situation. The function header is expected to belong into sourcemaps.
# if btoa? and JSON? and unescape? and encodeURIComponent?
# options = {
# bare: true
# sourceMap: true
# inline: true
# }
# {js, v3SourceMap} = CoffeeScript.compile source, options
# js = "#{js}\n//@ sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//@ sourceURL=#{sourceURL}"
# else
options = {
bare: true
sourceMap: false
}
js = CoffeeScript.compile source, options
Function("module", "exports", "require", "get", js)(module, exports, require, get)
module.loaded = true
return module.exports
class ArrayBufferWriter
constructor: (size) ->
@position = 0
@data = new Uint8Array(size)
init: (callback) =>
callback()
writeUint8Array: (array, callback) =>
data = @data
position = @position
for i in [0...array.length]
@data[i+position] = array[i]
@position += array.length
callback()
getData: (callback) =>
callback(@data)
class ZipReader
constructor: (blob, @params={}) ->
@zip = zip.createReader new zip.BlobReader(blob), (reader) =>
@reader = reader
if @reader == undefined
throw 'No zip reader'
@entries = {}
@totalSize = 0
@entryProgress = {}
@totalProgress = 0
@reader.getEntries (entries) =>
@loadCount = entries.length
for entry in entries
@totalSize += entry.uncompressedSize
@entryProgress[entry.filename] = 0
@getEntry entry
getEntry: (entry) ->
ext = entry.ext = entry.filename.split('.').pop()
switch ext
when 'jpeg', 'gif', 'png'
writer = new ArrayBufferWriter(entry.uncompressedSize)
when 'txt', 'json', 'coffee', 'js', 'glsl'
writer = new zip.TextWriter('utf-8')
else
writer = new ArrayBufferWriter(entry.uncompressedSize)
entry.getData(
writer,
((data) => @data(entry, data)),
((current, total) => @progress(entry, current, total)),
)
data: (entry, data) ->
switch entry.ext
when 'json'
data = JSON.parse(data)
@gotData entry, data
when 'jpeg', 'gif', 'png'
img = new Image()
img.onload = =>
img.array = data
@gotData entry, img
blob = new Blob [data], type:'image/' + entry.ext
img.src = URL.createObjectURL(blob)
else
@gotData entry, data
gotData: (entry, data) ->
@entries[entry.filename] = data
@loadCount -= 1
if @loadCount == 0 and @params.load?
@params.load @entries
progress: (entry, current, total) ->
last = @entryProgress[entry.filename]
@totalProgress -= @entryProgress[entry.filename]
@totalProgress += current
@entryProgress[entry.filename] = current
if @params.progress?
@params.progress @totalProgress/@totalSize
requestBlob = (url, options) ->
@xhr = new XMLHttpRequest()
@xhr.open('GET', url)
@xhr.responseType = 'blob'
@xhr.onload = () =>
if xhr.status == 200
if options.load?
options.load(@xhr.response)
else
if options.error?
options.error(Error(xhr.statusText))
@xhr.onerror = () ->
if options.error?
options.error(Error("Network Error"))
@xhr.send()
# $ -> boot("app.zip", "/scripts/main", (module) -> )
#
# boot = (url, entry_script, callback) ->
# callback ?= () ->
# fs = {
# entries: {}
# addModule: (path) ->
# path = parsePath(path)
# module = {path, fs:@}
# @entries[path.toString()] = module
# return module
# getModule: (path) ->
# s = path.toString()
# return @entries[s]
# get: (path) ->
# module = @getModule(path)
# return null unless module?
# if module.object?
# return module.object
# return module
# }
#
# success = () ->
# console.log "booting up"
# module = require_js(fs, entry_script)
# callback(module)
#
# mount_error = (reason) ->
# throw "#{url} mount failed: #{reason}"
#
# mount(fs, '/', url, success, mount_error)
#
#
# mount = (fs, dirname, url, on_success, on_error) ->
# dirname = parsePath(dirname)
# zipfs = new zip.fs.FS()
# count = 1
# count_up = () ->
# count += 1
# count_down = () ->
# count -= 1
# on_success() if count == 0
# mount_each = () ->
# for entry in zipfs.entries
# unless entry.directory
# path = get_entry_path(dirname, entry)
# count_up()
# mount_one(fs, path, entry, count_down, on_error)
# count_down()
# zipfs.importHttpContent(url, false, mount_each, on_error)
#
# # It might be possible to treat everything
# # as a blob filetype when mounted into fake
# # filesystem. That way the client code
# # could define handlers themselves. It's
# # better idea than predefining these handlers.
# mount_one = (fs, path, entry, on_success, on_error) ->
# [bpath, ext] = split_ext(path)
# window.entry = entry
# if ext == 'js'
# module = fs.addModule(bpath)
# entry_as_text entry, (text) ->
# module.type = 'js'
# module.sources = text
# module.loaded = false
# module.done = false
# module.exports = {}
# module.require = (path) -> require_js(fs, module.path.pop().concat(path))
# module.get = (path) -> fs.get(module.path.pop().concat(path))
#
# on_success()
# else if ext == 'glsl'
# module = fs.addModule(path)
# entry_as_text entry, (text) ->
# module.type = 'glsl'
# module.sources = text
# module.loaded = false
# on_success()
# # the rest of this loader will sit in glsl module.
# else if ext == 'png'
# module = fs.addModule(path)
# module.type = 'image'
# entry.getData64URI "image/png", (data_uri) ->
# module.object = new Image()
# module.object.src = data_uri
# module.loaded = true
# on_success()
# else
# module = fs.addModule(path)
# module.type = 'blob'
# entry.getBlob "", (blob) ->
# reader = new FileReader()
# reader.onload = () ->
# module.object = reader.result
# module.loaded = true
# on_success()
# reader.readAsArrayBuffer(blob)
#
# # entry.getText did not work on firefox 26.0 for some reason.
# # getting a blob and calling reader works instead.
# entry_as_text = (entry, success) ->
# entry.getBlob "", (blob) ->
# reader = new window.FileReader()
# reader.readAsText(blob)
# reader.onloadend = () -> success(reader.result)
#
# split_ext = (path) ->
# match = path.basename.match /^(.*?)\.([^/]*)$/
# return [path.basename, null] unless match?
# [m, basename, ext] = match
# bpath = path.pop().push(basename)
# return [bpath, ext]
#
# get_entry_path = (dirname, entry) ->
# if entry.parent?
# return get_entry_path(dirname, entry.parent).push(entry.name)
# return dirname
#
# string_ends_with = (self, suffix) ->
# return self.indexOf(suffix, self.length - suffix.length) != -1
#
# # The module cache assigns every script
# # an unique identifier, so they can fetch
# # their module from the global cache.
# window.module_cache = {}
# module_next_id = 1
#
# require_js = (fs, path) ->
# path = parsePath(path)
# module = fs.getModule(path)
# unless module?
# throw "no such module #{path.toString()}"
# if module.type != 'js'
# throw "not javascript file #{path.toString()}"
# unless module.loaded
# module.id = module_next_id++
# module.loaded = true
# window.module_cache[module.id] = module
# script = document.createElement('script')
# script.innerHTML = "(function() { var module = window.module_cache[\"#{module.id}\"]; var exports = module.exports; var get = module.get; var require = module.require; #{module.sources} module.exports = exports; }).call(this);"
# document.head.appendChild(script)
# module.done = true
# return module.exports
#
# Shifting around path strings becomes unreadable fast.
# Instead we use an explicit path object. It's
# more reliable and convenient way to handle strings.
class window.Path
constructor: (@raw, @is_absolute) ->
@basename = @raw[@raw.length - 1]
push: (args...) ->
raw = @raw.concat args
return new Path(raw, @is_absolute)
concat: (other) ->
other = parsePath(other)
if other.is_absolute
return other
else
return new Path(@raw.concat(other), @is_absolute)
pop: () ->
L = @raw.length
raw = @raw[...L-1]
name = @raw[L-1]
return new Path(raw, @is_absolute)
toString: (withPrefix=true) ->
prefix = ''
if @is_absolute and withPrefix
prefix = '/'
return prefix + @raw.join('/')
# String is coerced into a path object
# whenever it's used in place of a path.
window.parsePath = (path) ->
if typeof path != "string" # if it's not a string, we assume it's already a path.
return path
if path == '/'
return new Path([], true)
if path[0] == '/'
raw = path[1...].split('/')
return new Path(raw, true)
else
raw = path.split('/')
return new Path(raw, false)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment