aboutsummaryrefslogblamecommitdiffhomepage
path: root/lib/to-javascript.js
blob: ce4970440e574bc286b2bb8e96fdd2988edd703e (plain) (tree)























































































































































                                                                                                     
'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);
  });
};