aboutsummaryrefslogblamecommitdiffhomepage
path: root/lib/index.js
blob: ad45eac8c0397f03ebc9e96877c28f3ab9c81094 (plain) (tree)




























































































































                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

                    
















































































                                                                                                                                                                    
                                                                                                                         









                                                                        




























                                                                                                                                                                                                

















                                                                                                         
                                                         

                                                 
                                                     

                                                    
                                               













                                                                                                  


                                                    


                   

                                                                                                                                                                                      



                                           

                                          

                         

                                                                         

                           

                                           



























































































































































                                                                                           
'use strict';

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var debug_ = require('debug');

var debug = debug_('purs-loader');

var debugVerbose = debug_('purs-loader:verbose');

var loaderUtils = require('loader-utils');

var Promise = require('bluebird');

var path = require('path');

var PsModuleMap = require('./purs-module-map');

var compile = require('./compile');

var bundle = require('./bundle');

var ide = require('./ide');

var toJavaScript = require('./to-javascript');

var sourceMaps = require('./source-maps');

var dargs = require('./dargs');

var utils = require('./utils');

var spawn = require('cross-spawn').sync;

var eol = require('os').EOL;

var CACHE_VAR = {
  rebuild: false,
  deferred: [],
  bundleModules: [],
  ideServer: null,
  psModuleMap: null,
  warnings: [],
  errors: [],
  compilationStarted: false,
  compilationFinished: false,
  compilationFailed: false,
  installed: false,
  srcOption: []
};

module.exports = function purescriptLoader(source, map) {
  var _this = this;

  this.cacheable && this.cacheable();

  var webpackContext = this.options && this.options.context || this.rootContext;

  var callback = this.async();

  var loaderOptions = loaderUtils.getOptions(this) || {};

  var srcOption = function (pscPackage) {
    var srcPath = path.join('src', '**', '*.purs');

    var bowerPath = path.join('bower_components', 'purescript-*', 'src', '**', '*.purs');

    if (CACHE_VAR.srcOption.length > 0) {
      return CACHE_VAR.srcOption;
    } else if (pscPackage) {
      var pscPackageCommand = 'psc-package';

      var pscPackageArgs = ['sources'];

      var loaderSrc = loaderOptions.src || [srcPath];

      debug('psc-package %s %o', pscPackageCommand, pscPackageArgs);

      var cmd = spawn(pscPackageCommand, pscPackageArgs);

      if (cmd.error) {
        throw new Error(cmd.error);
      } else if (cmd.status !== 0) {
        var error = cmd.stdout.toString();

        throw new Error(error);
      } else {
        var result = cmd.stdout.toString().split(eol).filter(function (v) {
          return v != '';
        }).concat(loaderSrc);

        debug('psc-package result: %o', result);

        CACHE_VAR.srcOption = result;

        return result;
      }
    } else {
      var _result = loaderOptions.src || [bowerPath, srcPath];

      CACHE_VAR.srcOption = _result;

      return _result;
    }
  }(loaderOptions.pscPackage);

  var options = Object.assign({
    context: webpackContext,
    psc: null,
    pscArgs: {},
    pscBundle: null,
    pscBundleArgs: {},
    pscIdeClient: null,
    pscIdeClientArgs: {},
    pscIdeServer: null,
    pscIdeServerArgs: {},
    pscIde: false,
    pscIdeColors: loaderOptions.psc === 'psa',
    pscPackage: false,
    bundleOutput: 'output/bundle.js',
    bundleNamespace: 'PS',
    bundle: false,
    warnings: true,
    watch: false,
    output: 'output',
    src: [],
    rewriteRules: {}
  }, loaderOptions, {
    src: srcOption
  });

  if (!CACHE_VAR.installed) {
    debugVerbose('installing purs-loader with options: %O', options);

    CACHE_VAR.installed = true;

    // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode)
    this._compiler.plugin('invalid', function () {
      debugVerbose('invalidating loader CACHE_VAR');

      CACHE_VAR = {
        rebuild: options.pscIde,
        deferred: [],
        bundleModules: [],
        ideServer: CACHE_VAR.ideServer,
        psModuleMap: CACHE_VAR.psModuleMap,
        warnings: [],
        errors: [],
        compilationStarted: false,
        compilationFinished: false,
        compilationFailed: false,
        installed: CACHE_VAR.installed,
        srcOption: []
      };
    });

    // add psc warnings to webpack compilation warnings
    this._compiler.plugin('after-compile', function (compilation, callback) {
      CACHE_VAR.warnings.forEach(function (warning) {
        compilation.warnings.push(warning);
      });

      CACHE_VAR.errors.forEach(function (error) {
        compilation.errors.push(error);
      });

      callback();
    });
  }

  var psModuleName = PsModuleMap.matchModule(source);

  var psModule = {
    name: psModuleName,
    source: source,
    load: function load(_ref) {
      var js = _ref.js,
          map = _ref.map;
      return callback(null, js, map);
    },
    reject: function reject(error) {
      return callback(error);
    },
    srcPath: this.resourcePath,
    remainingRequest: loaderUtils.getRemainingRequest(this),
    srcDir: path.dirname(this.resourcePath),
    jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')),
    options: options,
    cache: CACHE_VAR,
    emitWarning: function emitWarning(warning) {
      if (options.warnings && warning.length) {
        CACHE_VAR.warnings.push(warning);
      }
    },
    emitError: function emitError(pscMessage) {
      if (pscMessage.length) {
        var modules = [];

        var matchErrorsSeparator = /\n(?=Error)/;
        var errors = pscMessage.split(matchErrorsSeparator);
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
          for (var _iterator = errors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            var error = _step.value;

            var matchErrLocation = /at (.+\.purs):(\d+):(\d+) - (\d+):(\d+) \(line \2, column \3 - line \4, column \5\)/;

            var _ref2 = matchErrLocation.exec(error) || [],
                _ref3 = _slicedToArray(_ref2, 2),
                filename = _ref3[1];

            if (!filename) continue;

            var baseModulePath = path.join(_this.rootContext, filename);
            _this.addDependency(baseModulePath);

            var foreignModulesErrorCodes = ['ErrorParsingFFIModule', 'MissingFFIImplementations', 'UnusedFFIImplementations', 'MissingFFIModule'];
            var _iteratorNormalCompletion2 = true;
            var _didIteratorError2 = false;
            var _iteratorError2 = undefined;

            try {
              for (var _iterator2 = foreignModulesErrorCodes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
                var code = _step2.value;

                if (error.includes(code)) {
                  var resolved = utils.resolveForeignModule(baseModulePath);
                  _this.addDependency(resolved);
                }
              }
            } catch (err) {
              _didIteratorError2 = true;
              _iteratorError2 = err;
            } finally {
              try {
                if (!_iteratorNormalCompletion2 && _iterator2.return) {
                  _iterator2.return();
                }
              } finally {
                if (_didIteratorError2) {
                  throw _iteratorError2;
                }
              }
            }

            var matchErrModuleName = /in module ((?:\w+\.)*\w+)/;

            var _ref4 = matchErrModuleName.exec(error) || [],
                _ref5 = _slicedToArray(_ref4, 2),
                baseModuleName = _ref5[1];

            if (!baseModuleName) continue;

            var matchMissingModuleName = /Module ((?:\w+\.)*\w+) was not found/;
            var matchMissingImportFromModuleName = /Cannot import value \w+ from module ((?:\w+\.)*\w+)/;
            var _arr = [matchMissingModuleName, matchMissingImportFromModuleName];
            for (var _i = 0; _i < _arr.length; _i++) {
              var re = _arr[_i];
              var _ref6 = re.exec(error) || [],
                  _ref7 = _slicedToArray(_ref6, 2),
                  targetModuleName = _ref7[1];

              if (targetModuleName) {
                var _resolved = utils.resolvePursModule({
                  baseModulePath: baseModulePath,
                  baseModuleName: baseModuleName,
                  rewriteRules: options.rewriteRules,
                  targetModuleName: targetModuleName
                });
                _this.addDependency(_resolved);
              }
            }

            var desc = {
              name: baseModuleName,
              filename: baseModulePath
            };

            if (typeof _this.describePscError === 'function') {
              var _describePscError = _this.describePscError(error, desc),
                  _describePscError$dep = _describePscError.dependencies,
                  dependencies = _describePscError$dep === undefined ? [] : _describePscError$dep,
                  details = _describePscError.details;

              var _iteratorNormalCompletion3 = true;
              var _didIteratorError3 = false;
              var _iteratorError3 = undefined;

              try {

                for (var _iterator3 = dependencies[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
                  var dep = _step3.value;

                  _this.addDependency(dep);
                }
              } catch (err) {
                _didIteratorError3 = true;
                _iteratorError3 = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion3 && _iterator3.return) {
                    _iterator3.return();
                  }
                } finally {
                  if (_didIteratorError3) {
                    throw _iteratorError3;
                  }
                }
              }

              Object.assign(desc, details);
            }

            modules.push(desc);
          }
        } catch (err) {
          _didIteratorError = true;
          _iteratorError = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion && _iterator.return) {
              _iterator.return();
            }
          } finally {
            if (_didIteratorError) {
              throw _iteratorError;
            }
          }
        }

        CACHE_VAR.errors.push(new utils.PscError(pscMessage, modules));
      }
    }
  };

  debug('loading %s', psModule.name);

  if (options.bundle) {
    CACHE_VAR.bundleModules.push(psModule.name);
  }

  if (CACHE_VAR.rebuild) {
    var connect = function connect() {
      if (!CACHE_VAR.ideServer) {
        CACHE_VAR.ideServer = true;

        return ide.connect(psModule).then(function (ideServer) {
          CACHE_VAR.ideServer = ideServer;
          return psModule;
        }).then(ide.loadWithRetry).catch(function (error) {
          if (CACHE_VAR.ideServer.kill) {
            debug('ide failed to initially load modules, stopping the ide server process');

            CACHE_VAR.ideServer.kill();
          }

          CACHE_VAR.ideServer = null;

          return Promise.reject(error);
        });
      } else {
        return Promise.resolve(psModule);
      }
    };

    var rebuild = function rebuild() {
      return ide.rebuild(psModule).then(function () {
        return toJavaScript(psModule).then(function (js) {
          return sourceMaps(psModule, js);
        }).then(psModule.load).catch(psModule.reject);
      }).catch(function (error) {
        if (error instanceof ide.UnknownModuleError) {
          // Store the modules that trigger a recompile due to an
          // unknown module error. We need to wait until compilation is
          // done before loading these files.

          CACHE_VAR.deferred.push(psModule);

          if (!CACHE_VAR.compilationStarted) {
            CACHE_VAR.compilationStarted = true;

            return compile(psModule).then(function () {
              CACHE_VAR.compilationFinished = true;
            }).then(function () {
              return Promise.map(CACHE_VAR.deferred, function (psModule) {
                return ide.load(psModule).then(function () {
                  return toJavaScript(psModule);
                }).then(function (js) {
                  return sourceMaps(psModule, js);
                }).then(psModule.load);
              });
            }).catch(function (error) {
              CACHE_VAR.compilationFailed = true;

              CACHE_VAR.deferred[0].reject(error);

              CACHE_VAR.deferred.slice(1).forEach(function (psModule) {
                psModule.reject(new Error('purs-loader failed'));
              });
            });
          } else if (CACHE_VAR.compilationFailed) {
            CACHE_VAR.deferred.pop().reject(new Error('purs-loader failed'));
          } else {
            // The compilation has started. We must wait until it is
            // done in order to ensure the module map contains all of
            // the unknown modules.
          }
        } else {
          debug('ide rebuild failed due to an unhandled error: %o', error);

          psModule.reject(error);
        }
      });
    };

    connect().then(rebuild);
  } else if (CACHE_VAR.compilationFinished) {
    debugVerbose('compilation is already finished, loading module %s', psModule.name);

    toJavaScript(psModule).then(function (js) {
      return sourceMaps(psModule, js);
    }).then(psModule.load).catch(psModule.reject);
  } else {
    // The compilation has not finished yet. We need to wait for
    // compilation to finish before the loaders run so that references
    // to compiled output are valid. Push the modules into the CACHE_VAR to
    // be loaded once the complation is complete.

    CACHE_VAR.deferred.push(psModule);

    if (!CACHE_VAR.compilationStarted) {
      CACHE_VAR.compilationStarted = true;

      compile(psModule).then(function () {
        CACHE_VAR.compilationFinished = true;
      }).then(function () {
        if (options.bundle) {
          return bundle(options, CACHE_VAR.bundleModules);
        }
      }).then(function () {
        return Promise.map(CACHE_VAR.deferred, function (psModule) {
          return toJavaScript(psModule).then(function (js) {
            return sourceMaps(psModule, js);
          }).then(psModule.load);
        });
      }).catch(function (error) {
        CACHE_VAR.compilationFailed = true;

        CACHE_VAR.deferred[0].reject(error);

        CACHE_VAR.deferred.slice(1).forEach(function (psModule) {
          psModule.reject(new Error('purs-loader failed'));
        });
      });
    } else if (CACHE_VAR.compilationFailed) {
      CACHE_VAR.deferred.pop().reject(new Error('purs-loader failed'));
    } else {
      // The complation has started. Nothing to do but wait until it is
      // done before loading all of the modules.
    }
  }
};