'use strict'; var Promise = require('bluebird'); var fs = Promise.promisifyAll(require('fs')); var path = require('path'); var jsStringEscape = require('js-string-escape'); var difference = require('lodash.difference'); var debug_ = require('debug'); var debug = debug_('purs-loader'); var debugVerbose = debug_('purs-loader:verbose'); var PsModuleMap = require('./purs-module-map'); function updatePsModuleMap(psModule) { var options = psModule.options; var cache = psModule.cache; var filePurs = psModule.srcPath; if (!cache.psModuleMap) { debugVerbose('module mapping does not exist - making a new module map'); cache.psModuleMap = PsModuleMap.makeMap(options.src); return cache.psModuleMap; } else { debugVerbose('module mapping exists - updating module map for %s', filePurs); cache.psModuleMap = cache.psModuleMap.then(function (psModuleMap) { return PsModuleMap.makeMapEntry(filePurs).then(function (result) { var map = Object.assign(psModuleMap, result); return map; }); }); return cache.psModuleMap; } } // Reference the bundle. function makeBundleJS(psModule) { var bundleOutput = psModule.options.bundleOutput; var name = psModule.name; var srcDir = psModule.srcDir; var escaped = jsStringEscape(path.relative(srcDir, bundleOutput)); var result = 'module.exports = require("' + escaped + '")["' + name + '"]'; return Promise.resolve(result); } // Replace require paths to output files generated by psc with paths // to purescript sources, which are then also run through this loader. // Additionally, the imports replaced are tracked so that in the event // the compiler fails to compile the PureScript source, we can tack on // any new imports in order to allow webpack to watch the new files // before they have been successfully compiled. function makeJS(psModule, psModuleMap, js) { var requireRE = /require\(['"]\.\.\/([\w\.]+)(?:\/index\.js)?['"]\)/g; var foreignRE = /require\(['"]\.\/foreign(?:\.js)?['"]\)/g; var name = psModule.name; var imports = psModuleMap[name].imports; var replacedImports = []; var result = js.replace(requireRE, function (m, p1) { var moduleValue = psModuleMap[p1]; if (!moduleValue) { debug('module %s was not found in the map, replacing require with null', p1); return 'null'; } else { var escapedPath = jsStringEscape(moduleValue.src); replacedImports.push(p1); return 'require("' + escapedPath + '")'; } }).replace(foreignRE, function () { var escapedPath = jsStringEscape(psModuleMap[name].ffi); return 'require("' + escapedPath + '")'; }); var additionalImports = difference(imports, replacedImports); if (!additionalImports.length) { return Promise.resolve(result); } else { debug('rebuilding module map due to additional imports for %s: %o', name, additionalImports); psModule.cache.psModuleMap = null; return updatePsModuleMap(psModule).then(function (updatedPsModuleMap) { var additionalImportsResult = additionalImports.map(function (import_) { var moduleValue = updatedPsModuleMap[import_]; if (!moduleValue) { debug('module %s was not found in the map, skipping require', import_); return null; } else { var escapedPath = jsStringEscape(moduleValue.src); return 'var ' + import_.replace(/\./g, '_') + ' = require("' + escapedPath + '")'; } }).filter(function (a) { return a !== null; }).join('\n'); return result + '\n' + additionalImportsResult; }); } } module.exports = function toJavaScript(psModule) { var options = psModule.options; var cache = psModule.cache; var bundlePath = path.resolve(options.bundleOutput); var jsPath = options.bundle ? bundlePath : psModule.jsPath; var js = fs.readFileAsync(jsPath, 'utf8').catch(function () { return ''; }); var psModuleMap = updatePsModuleMap(psModule); debugVerbose('loading JavaScript for %s', psModule.name); return Promise.props({ js: js, psModuleMap: psModuleMap }).then(function (result) { return options.bundle ? makeBundleJS(psModule) : makeJS(psModule, result.psModuleMap, result.js); }); };