browser-build
Version:
Makes commonjs modules available in the browser via window.require('module-name')
250 lines (222 loc) • 9.26 kB
text/coffeescript
require 'sugar'
utility = require "./utility"
watcher = require "./watcher"
np = require "path"
fs = require 'fs'
log = (config, message) ->
console.log message unless config.silent
normalize = (file) -> file.replace /\\/g, "\/"
getInputKeyValueForFile = (config, file) ->
# normalize file name to / format
file = normalize file
for name, input of config.input when Object.isString input
if file.startsWith input
return [name,input]
throw new Error "input not found in config.input: #{file}"
getInputDirectory = (config, file) ->
getInputKeyValueForFile(config, file)[1]
getBaseModuleName = (config, file) ->
getInputKeyValueForFile(config, file)[0]
getOutputModuleDirectory = (config) ->
np.join config.output.directory, "modules"
getOutputFile = (config, file, id) ->
file = np.normalize np.join(getOutputModuleDirectory(config), id) + ".js"
deleteFile = (file) ->
if fs.existsSync file
fs.unlinkSync file
buildBrowserTestFile = (config) ->
type = config.output.test
return unless type?
testFolder = np.join(__dirname, "../test", type)
if not fs.existsSync testFolder
throw new Error "Test files for #{type} not found, expected at #{testFolder}"
# copy this folder to the output test folder
outputFolder = np.join config.output.directory, "test"
# copy our test files to the output folder
utility.copy testFolder, outputFolder
copySourceMap = (config, inputFile, outputFile, deleteOutput = false) ->
mapInput = inputFile.replace /\.js$/, ".map"
mapOutput = outputFile.replace /\.js$/, ".map"
if fs.existsSync mapInput
try
map = JSON.parse utility.read mapInput
catch e
# this can happen sometimes if we're reading
# while this file is getting written by coffee -c
map =
sourceRoot: "."
sources: []
# modify the sourceRoot and sources
# to expect the source to be in the same directory
# find and copy each source to the output directory
for source, index in map.sources
source = np.join np.dirname(mapInput), map.sourceRoot, source
copyTo = np.join np.dirname(mapOutput), shortName = source.split(/[\/\\]/g).pop()
if deleteOutput
deleteFile copyTo
else
if not fs.existsSync source
# console.warn "Source not found: #{mapInput} -> #{source}"
continue
utility.copy source, copyTo
# also change the source reference to the shortName
map.sources[index] = shortName
# finally, make the source root the current directory
map.sourceRoot = "."
# then write out the sourceMap inputFile
if deleteOutput
deleteFile mapInput
deleteFile mapOutput
else
utility.write mapOutput, JSON.stringify map, null, ' '
buildMap = {}
buildFile = (config, file, id) ->
# check build map to avoid duplicates
previousFile = buildMap[id]
if previousFile? and previousFile isnt file
return
buildMap[id] = file
outputFile = getOutputFile config, file, id
if not fs.existsSync file
# delete source map files
copySourceMap config, file, outputFile, true
deleteFile outputFile
return
input = utility.read file
output = "(function(){require.register('#{id}',function(module,exports,require){#{input}\n})})()"
utility.write outputFile, output
log config, "Wrapped #{outputFile}"
copySourceMap config, file, outputFile
buildIncludes = (config) ->
list = utility.list getOutputModuleDirectory(config), {include:".js"}
list = list.map (x) ->
normalize np.relative(config.output.directory, x)
# sort by shallowest directory, then alphabetical
list = list.sort (a, b) ->
aa = a.split '/'
bb = b.split '/'
if aa.length != bb.length
return aa.length - bb.length
for aitem, index in aa
bitem = bb[index]
compare = aitem.localeCompare bitem
if compare != 0
return compare
return 0
script = ""
# figure out the include base relative to the web root.
webroot = config.output.webroot ? config.output.directory
base = np.relative(webroot, config.output.directory).replace(/\\/g, '\/')
base = "/#{base}/".replace(/\/+/, '/')
for file in list
script += """document.writeln("<script src='#{base}#{file}'></script>");\n"""
includeFile = getDebugIncludeFile config
utility.write includeFile, script
log config, "Created #{includeFile}"
# also build the manifest
manifest = getDebugManifestFile config
manifestParent = np.dirname manifest
manifestList = list.map (x) -> normalize np.relative manifestParent, np.join(config.output.directory, x)
utility.write getDebugManifestFile(config), JSON.stringify(manifestList, null, " ")
# also build the test file
buildBrowserTestFile config
getDebugIncludeFile = (config) -> np.join config.output.directory, "debug.js"
getDebugManifestFile = (config) -> np.join config.output.directory, "modules/manifest.json"
copyRequire = (config) ->
# copy the require from our source to the output directory
source = np.join __dirname, 'require.js'
# in case we're running from .coffee sources
if not fs.existsSync source
source = np.join __dirname, '../lib/require.js'
target = np.join getOutputModuleDirectory(config), 'require.js'
utility.copy source, target
log config, "Copied #{target}"
check = (config) ->
config.input ?= {"": true}
resolve = (root, module, source) ->
paths = [
root
np.join(root, "node_modules")
np.join(root, "../node_modules")
np.join(root, "../../node_modules")
]
if process.env.NODE_PATH?
paths = paths.concat process.env.NODE_PATH.split np.delimiter
for path in paths
main = np.join path, module + "/package.json"
if fs.existsSync main
json = eval "(#{utility.read main})"
module = json.name
if json.dependencies?
for name of json.dependencies
fixOptions name, true, np.dirname(main), main
main = np.join np.dirname(main), (json.main ? "index.js")
return [module,main]
main = np.join path, module + ".js"
if fs.existsSync main
return [module,main]
main = np.join path, module + "/index.js"
if fs.existsSync main
return [module,main]
throw new Error "module not found: " + module + ", source: " + source
fixOptions = (name, options, root, source) ->
# remove original value
delete config.input[name]
if Object.isString options
options =
name: name
main: 'index.js'
directory: np.normalize options
else if options is true
[name,main] = resolve root, name, source
options =
name: name
main: np.basename main
directory: np.dirname main
# set new value
config.input[name] = options
for key, value of config.input
fixOptions key, value, '.', 'config'
return
buildCommon = (config) ->
check config
copyRequire config
getModuleId = (inputConfig, file) ->
relative = np.relative inputConfig.directory, file
if relative is inputConfig.main
relative = "index.js"
# remove extension
relative = relative.slice 0, -".js".length
normalize np.join inputConfig.name, relative
exclude = (file) ->
if /WEB-INF/.test file
return true
return false
exports.build = (config, callback) ->
buildCommon config
for name, input of config.input
list = utility.list input.directory, {include: ".js"}
for file in list when not exclude file
id = getModuleId input, file
buildFile config, file, id
buildIncludes config
callback?()
watchInput = (config, input) ->
watcher.watchDirectory input.directory, {include: ".js", initial:false},
(file, curr, prev, change) ->
if exclude file
return
id = getModuleId input, file
buildFile config, file, id
if change is "deleted" or change is "created"
buildIncludes config
else
# touch the manifest file so others can fs.watch it
utility.touch getDebugManifestFile config
exports.watch = (config) ->
exports.build config
for name, input of config.input
watchInput config, input
# re-export utility and watcher
exports.utility = utility
exports.watcher = watcher