X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=src%2Findex.js;h=be809c685ac5959cc011388259cea1026578102c;hb=c69d78d9010e224f1a31b1acfa24db346d535cb6;hp=19a549e3aa06b8bbf15b1f87991c6260ebabdfdc;hpb=e17196589a380eed3ffc47705f8c6c87f99b58c2;p=github%2Ffretlink%2Fpurs-loader.git diff --git a/src/index.js b/src/index.js index 19a549e..be809c6 100644 --- a/src/index.js +++ b/src/index.js @@ -22,30 +22,35 @@ const ide = require('./ide'); const toJavaScript = require('./to-javascript'); +const sourceMaps = require('./source-maps'); + const dargs = require('./dargs'); +const utils = require('./utils'); + const spawn = require('cross-spawn').sync const 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) { this.cacheable && this.cacheable(); - const webpackConfig = this.options; - - var cache = webpackConfig.purescriptLoaderCache = webpackConfig.purescriptLoaderCache || { - rebuild: false, - deferred: [], - bundleModules: [], - ideServer: null, - psModuleMap: null, - warnings: [], - errors: [], - compilationStarted: false, - compilationFinished: false, - installed: false, - srcOption: [] - }; + const webpackContext = (this.options && this.options.context) || this.rootContext; const callback = this.async(); @@ -56,8 +61,8 @@ module.exports = function purescriptLoader(source, map) { const bowerPath = path.join('bower_components', 'purescript-*', 'src', '**', '*.purs'); - if (cache.srcOption.length > 0) { - return cache.srcOption; + if (CACHE_VAR.srcOption.length > 0) { + return CACHE_VAR.srcOption; } else if (pscPackage) { const pscPackageCommand = 'psc-package'; @@ -72,7 +77,10 @@ module.exports = function purescriptLoader(source, map) { const cmd = spawn(pscPackageCommand, pscPackageArgs); - if (cmd.status !== 0) { + if (cmd.error) { + throw new Error(cmd.error); + } + else if (cmd.status !== 0) { const error = cmd.stdout.toString(); throw new Error(error); @@ -82,7 +90,7 @@ module.exports = function purescriptLoader(source, map) { debug('psc-package result: %o', result); - cache.srcOption = result; + CACHE_VAR.srcOption = result; return result; } @@ -93,21 +101,24 @@ module.exports = function purescriptLoader(source, map) { srcPath ]; - cache.srcOption = result; + CACHE_VAR.srcOption = result; return result; } })(loaderOptions.pscPackage); const options = Object.assign({ - context: webpackConfig.context, + context: webpackContext, psc: null, pscArgs: {}, pscBundle: null, pscBundleArgs: {}, + pscIdeClient: null, + pscIdeClientArgs: {}, + pscIdeServer: null, + pscIdeServerArgs: {}, pscIde: false, pscIdeColors: loaderOptions.psc === 'psa', - pscIdeArgs: {}, pscPackage: false, bundleOutput: 'output/bundle.js', bundleNamespace: 'PS', @@ -120,37 +131,38 @@ module.exports = function purescriptLoader(source, map) { src: srcOption }); - if (!cache.installed) { + if (!CACHE_VAR.installed) { debugVerbose('installing purs-loader with options: %O', options); - cache.installed = true; + CACHE_VAR.installed = true; - // invalidate loader cache when bundle is marked as invalid (in watch mode) + // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode) this._compiler.plugin('invalid', () => { - debugVerbose('invalidating loader cache'); + debugVerbose('invalidating loader CACHE_VAR'); - cache = webpackConfig.purescriptLoaderCache = { + CACHE_VAR = { rebuild: options.pscIde, deferred: [], bundleModules: [], - ideServer: cache.ideServer, - psModuleMap: cache.psModuleMap, + ideServer: CACHE_VAR.ideServer, + psModuleMap: CACHE_VAR.psModuleMap, warnings: [], errors: [], - compilationStarted: cache.compilationStarted, - compilationFinished: cache.compilationFinished, - installed: cache.installed, - srcOption: cache.srcOption + compilationStarted: false, + compilationFinished: false, + compilationFailed: false, + installed: CACHE_VAR.installed, + srcOption: [] }; }); // add psc warnings to webpack compilation warnings this._compiler.plugin('after-compile', (compilation, callback) => { - cache.warnings.forEach(warning => { + CACHE_VAR.warnings.forEach(warning => { compilation.warnings.push(warning); }); - cache.errors.forEach(error => { + CACHE_VAR.errors.forEach(error => { compilation.errors.push(error); }); @@ -162,21 +174,84 @@ module.exports = function purescriptLoader(source, map) { const psModule = { name: psModuleName, - load: js => callback(null, js), + source: source, + load: ({js, map}) => callback(null, js, map), reject: error => 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, + cache: CACHE_VAR, emitWarning: warning => { if (options.warnings && warning.length) { - cache.warnings.push(warning); + CACHE_VAR.warnings.push(warning); } }, - emitError: error => { - if (error.length) { - cache.errors.push(error); + emitError: pscMessage => { + if (pscMessage.length) { + const modules = []; + + const matchErrorsSeparator = /\n(?=Error)/; + const errors = pscMessage.split(matchErrorsSeparator); + for (const error of errors) { + const matchErrLocation = /at (.+\.purs):(\d+):(\d+) - (\d+):(\d+) \(line \2, column \3 - line \4, column \5\)/; + const [, filename] = matchErrLocation.exec(error) || []; + if (!filename) continue; + + const baseModulePath = path.join(this.rootContext, filename); + this.addDependency(baseModulePath); + + const foreignModulesErrorCodes = [ + 'ErrorParsingFFIModule', + 'MissingFFIImplementations', + 'UnusedFFIImplementations', + 'MissingFFIModule' + ]; + for (const code of foreignModulesErrorCodes) { + if (error.includes(code)) { + const resolved = utils.resolveForeignModule(baseModulePath); + this.addDependency(resolved); + } + } + + const matchErrModuleName = /in module ((?:\w+\.)*\w+)/; + const [, baseModuleName] = matchErrModuleName.exec(error) || []; + if (!baseModuleName) continue; + + const matchMissingModuleName = /Module ((?:\w+\.)*\w+) was not found/; + const matchMissingImportFromModuleName = /Cannot import value \w+ from module ((?:\w+\.)*\w+)/; + for (const re of [matchMissingModuleName, matchMissingImportFromModuleName]) { + const [, targetModuleName] = re.exec(error) || []; + if (targetModuleName) { + const resolved = utils.resolvePursModule({ + baseModulePath, + baseModuleName, + targetModuleName + }); + this.addDependency(resolved); + } + } + + const desc = { + name: baseModuleName, + filename: baseModulePath + }; + + if (typeof this.describePscError === 'function') { + const { dependencies = [], details } = this.describePscError(error, desc); + + for (const dep of dependencies) { + this.addDependency(dep); + } + + Object.assign(desc, details); + } + + modules.push(desc); + } + + CACHE_VAR.errors.push(new utils.PscError(pscMessage, modules)); } } } @@ -184,28 +259,28 @@ module.exports = function purescriptLoader(source, map) { debug('loading %s', psModule.name); if (options.bundle) { - cache.bundleModules.push(psModule.name); + CACHE_VAR.bundleModules.push(psModule.name); } - if (cache.rebuild) { + if (CACHE_VAR.rebuild) { const connect = () => { - if (!cache.ideServer) { - cache.ideServer = true; + if (!CACHE_VAR.ideServer) { + CACHE_VAR.ideServer = true; return ide.connect(psModule) .then(ideServer => { - cache.ideServer = ideServer; + CACHE_VAR.ideServer = ideServer; return psModule; }) .then(ide.loadWithRetry) .catch(error => { - if (cache.ideServer.kill) { + if (CACHE_VAR.ideServer.kill) { debug('ide failed to initially load modules, stopping the ide server process'); - cache.ideServer.kill(); + CACHE_VAR.ideServer.kill(); } - cache.ideServer = null; + CACHE_VAR.ideServer = null; return Promise.reject(error); }) @@ -217,94 +292,112 @@ module.exports = function purescriptLoader(source, map) { }; const rebuild = () => - ide.rebuild(psModule).catch(error => { + ide.rebuild(psModule) + .then(() => + toJavaScript(psModule) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) + .catch(psModule.reject) + ) + .catch(error => { if (error instanceof ide.UnknownModuleError) { - if (!cache.compilationStarted) { - cache.compilationStarted = true; + // 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(() => { - cache.compilationFinished = true; + CACHE_VAR.compilationFinished = true; }) .then(() => - PsModuleMap.makeMap(options.src).then(map => { - debug('rebuilt module map after unknown module forced a recompilation'); + Promise.map(CACHE_VAR.deferred, psModule => + ide.load(psModule) + .then(() => toJavaScript(psModule)) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) + ) + ) + .catch(error => { + CACHE_VAR.compilationFailed = true; + + CACHE_VAR.deferred[0].reject(error); - cache.psModuleMap = map; + CACHE_VAR.deferred.slice(1).forEach(psModule => { + psModule.reject(new Error('purs-loader failed')); }) - ) - .then(() => ide.load(psModule)) - .then(() => psModule) + }) ; - } - else { - return Promise.resolve(psModule); + } 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); - return Promise.reject(error); + psModule.reject(error); } }) ; - connect() - .then(rebuild) - .then(toJavaScript) - .then(psModule.load) - .catch(psModule.reject) - ; + connect().then(rebuild); } - else if (cache.compilationFinished) { + else if (CACHE_VAR.compilationFinished) { debugVerbose('compilation is already finished, loading module %s', psModule.name); toJavaScript(psModule) + .then(js => 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 to + // to compiled output are valid. Push the modules into the CACHE_VAR to // be loaded once the complation is complete. - cache.deferred.push(psModule); + CACHE_VAR.deferred.push(psModule); - if (!cache.compilationStarted) { - cache.compilationStarted = true; + if (!CACHE_VAR.compilationStarted) { + CACHE_VAR.compilationStarted = true; compile(psModule) .then(() => { - cache.compilationFinished = true; + CACHE_VAR.compilationFinished = true; }) .then(() => { if (options.bundle) { - return bundle(options, cache.bundleModules); + return bundle(options, CACHE_VAR.bundleModules); } }) .then(() => - PsModuleMap.makeMap(options.src).then(map => { - debug('rebuilt module map after compilation'); - - cache.psModuleMap = map; - }) - ) - .then(() => - Promise.map(cache.deferred, psModule => - toJavaScript(psModule).then(psModule.load) + Promise.map(CACHE_VAR.deferred, psModule => + toJavaScript(psModule) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) ) ) .catch(error => { - cache.deferred[0].reject(error); + CACHE_VAR.compilationFailed = true; + + CACHE_VAR.deferred[0].reject(error); - cache.deferred.slice(1).forEach(psModule => { + CACHE_VAR.deferred.slice(1).forEach(psModule => { psModule.reject(new Error('purs-loader failed')); }) }) ; - } - else { + } 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. }