### Cache for source code transpiled by 6to5. Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf705f2c3/src/coffee-cache.coffee. ### crypto = require 'crypto' fs = require 'fs-plus' path = require 'path' to5 = null # Defer until used stats = hits: 0 misses: 0 defaultOptions = # The Chrome dev tools will show the original version of the file # when the source map is inlined. sourceMap: 'inline' # Because Atom is currently packaged with a fork of React v0.11, # it makes sense to use the --react-compat option so the React # JSX transformer produces pre-v0.12 code. reactCompat: true # Blacklisted features do not get transpiled. Features that are # natively supported in the target environment should be listed # here. Because Atom uses a bleeding edge version of Node/io.js, # I think this can include es6.arrowFunctions, es6.classes, and # possibly others, but I want to be conservative. blacklist: [ 'useStrict' ] # Includes support for es7 features listed at: # http://6to5.org/docs/usage/transformers/#es7-experimental-. experimental: true optional: [ # Target a version of the regenerator runtime that # supports yield so the transpiled code is cleaner/smaller. 'asyncToGenerator' ] ### shasum - Hash with an update() method. value - Must be a value that could be returned by JSON.parse(). ### updateDigestForJsonValue = (shasum, value) -> # Implmentation is similar to that of pretty-printing a JSON object, except: # * Strings are not escaped. # * No effort is made to avoid trailing commas. # These shortcuts should not affect the correctness of this function. type = typeof value if type is 'string' shasum.update('"', 'utf8') shasum.update(value, 'utf8') shasum.update('"', 'utf8') else if type in ['boolean', 'number'] shasum.update(value.toString(), 'utf8') else if value is null shasum.update('null', 'utf8') else if Array.isArray value shasum.update('[', 'utf8') for item in value updateDigestForJsonValue(shasum, item) shasum.update(',', 'utf8') shasum.update(']', 'utf8') else # value must be an object: be sure to sort the keys. keys = Object.keys value keys.sort() shasum.update('{', 'utf8') for key in keys updateDigestForJsonValue(shasum, key) shasum.update(': ', 'utf8') updateDigestForJsonValue(shasum, value[key]) shasum.update(',', 'utf8') shasum.update('}', 'utf8') create6to5VersionAndOptionsDigest = (version, options) -> shasum = crypto.createHash('sha1') # Include the version of 6to5 in the hash. shasum.update('6to5-core', 'utf8') shasum.update('\0', 'utf8') shasum.update(version, 'utf8') shasum.update('\0', 'utf8') updateDigestForJsonValue(shasum, options) shasum.digest('hex') jsCacheDir = null getCachePath = (sourceCode) -> digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex') unless jsCacheDir? to5Version = require('6to5-core/package.json').version cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache') jsCacheDir = path.join(cacheDir, 'js', '6to5', create6to5VersionAndOptionsDigest(to5Version, defaultOptions)) path.join(jsCacheDir, "#{digest}.js") getCachedJavaScript = (cachePath) -> if fs.isFileSync(cachePath) try cachedJavaScript = fs.readFileSync(cachePath, 'utf8') stats.hits++ return cachedJavaScript null # Returns the 6to5 options that should be used to transpile filePath. createOptions = (filePath) -> options = filename: filePath for key, value of defaultOptions options[key] = value options transpile = (sourceCode, filePath, cachePath) -> options = createOptions(filePath) to5 ?= require '6to5-core' js = to5.transform(sourceCode, options).code stats.misses++ try fs.writeFileSync(cachePath, js) js # Function that obeys the contract of an entry in the require.extensions map. # Returns the transpiled version of the JavaScript code at filePath, which is # either generated on the fly or pulled from cache. loadFile = (module, filePath) -> sourceCode = fs.readFileSync(filePath, 'utf8') unless sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") return module._compile(sourceCode, filePath) cachePath = getCachePath(sourceCode) js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath) module._compile(js, filePath) register = -> Object.defineProperty(require.extensions, '.js', { writable: false value: loadFile }) module.exports = register: register getCacheMisses: -> stats.misses getCacheHits: -> stats.hits # Visible for testing. create6to5VersionAndOptionsDigest: create6to5VersionAndOptionsDigest addPathToCache: (filePath) -> return if path.extname(filePath) isnt '.js' sourceCode = fs.readFileSync(filePath, 'utf8') cachePath = getCachePath(sourceCode) transpile(sourceCode, filePath, cachePath)