Created
January 29, 2014 19:43
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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