X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=src%2Findex.js;h=ff1330232ec0b6d764f2828e85535357a55d75a4;hb=1029c0322c943313bcdd28145d518fbc83f661c7;hp=6fb2fcef765a9cbe2fadec03148f14c51c5dd8d5;hpb=4305f5b0053d6fd2887b364c5da0a1ca6c06fc54;p=github%2Ffretlink%2Fpurs-loader.git diff --git a/src/index.js b/src/index.js index 6fb2fce..ff13302 100644 --- a/src/index.js +++ b/src/index.js @@ -1,164 +1,385 @@ 'use strict' -const debug = require('debug')('purs-loader') +const debug_ = require('debug'); + +const debug = debug_('purs-loader'); + +const debugVerbose = debug_('purs-loader:verbose'); + const loaderUtils = require('loader-utils') + const Promise = require('bluebird') + const path = require('path') -const PsModuleMap = require('./PsModuleMap'); -const Psc = require('./Psc'); -const PscIde = require('./PscIde'); + +const PsModuleMap = require('./purs-module-map'); + +const compile = require('./compile'); + +const bundle = require('./bundle'); + +const ide = require('./ide'); + const toJavaScript = require('./to-javascript'); -const dargs = require('./dargs'); + +const sourceMaps = require('./source-maps'); + 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, + installed: false, + srcOption: [], + spagoOutputPath: null +}; + +// include src files provided by psc-package or Spago +function requestDependencySources(packagerCommand, srcPath, loaderOptions) { + const packagerArgs = ['sources']; + + const loaderSrc = loaderOptions.src || [ + srcPath + ]; + + debug('%s %o', packagerCommand, packagerArgs); + + const cmd = spawn(packagerCommand, packagerArgs); + + if (cmd.error) { + throw new Error(cmd.error); + } + else if (cmd.status !== 0) { + const error = cmd.stdout.toString(); + + throw new Error(error); + } + else { + const result = cmd.stdout.toString().split(eol).filter(v => v != '').concat(loaderSrc); + + debug('%s result: %o', packagerCommand, result); + + CACHE_VAR.srcOption = result; + + return result; + } +} + +// 'spago output path' will return the output folder in a monorepo +function getSpagoSources() { + const cachedVal = CACHE_VAR.spagoOutputPath; + if (cachedVal) { + return cachedVal + } + const command = "spago" + const args = ["path", "output"] + + const cmd = spawn(command, args); + + if (cmd.error) { + throw new Error(cmd.error); + } + else if (cmd.status !== 0) { + const error = cmd.stdout.toString(); + + throw new Error(error); + } + else { + const result = cmd.stdout.toString().split(eol)[0] + + debug('"spago path output" result: %o', result); + + CACHE_VAR.spagoOutputPath = result; + + return result; + } +} + module.exports = function purescriptLoader(source, map) { - const callback = this.async() - const config = this.options - const query = loaderUtils.parseQuery(this.query) - const webpackOptions = this.options.purescriptLoader || {} + this.cacheable && this.cacheable(); - const depsPaths = (pscPackage => { - if (pscPackage) { - debug('calling psc-package...') + const webpackContext = (this.options && this.options.context) || this.rootContext; - return spawn('psc-package', ['sources']).stdout.toString().split(eol).filter(v => v != '') + const callback = this.async(); + + const loaderOptions = loaderUtils.getOptions(this) || {}; + + const srcOption = ((pscPackage, spago) => { + const srcPath = path.join('src', '**', '*.purs'); + + const bowerPath = path.join('bower_components', 'purescript-*', 'src', '**', '*.purs'); + + if (CACHE_VAR.srcOption.length > 0) { + return CACHE_VAR.srcOption; } - else { - return [ path.join('bower_components', 'purescript-*', 'src', '**', '*.purs') ] + else if (pscPackage) { + return requestDependencySources('psc-package', srcPath, loaderOptions) } - }) + else if (spago) { + return requestDependencySources('spago', srcPath, loaderOptions) + } + else { + const result = loaderOptions.src || [ + bowerPath, + srcPath + ]; + + CACHE_VAR.srcOption = result; - let options = Object.assign(webpackOptions, query) + return result; + } + })(loaderOptions.pscPackage, loaderOptions.spago); + + const outputPath = loaderOptions.spago ? getSpagoSources() : 'output' - const defaultDeps = depsPaths(options.pscPackage) - const defaultOptions = { - context: config.context, - psc: 'psc', + const options = Object.assign({ + context: webpackContext, + psc: null, pscArgs: {}, - pscBundle: 'psc-bundle', + pscBundle: null, pscBundleArgs: {}, + pscIdeClient: null, + pscIdeClientArgs: {}, + pscIdeServer: null, + pscIdeServerArgs: {}, + pscIdeRebuildArgs: {}, pscIde: false, - pscIdeColors: options.psc === 'psa', - pscIdeArgs: {}, + pscIdeColors: loaderOptions.psc === 'psa', pscPackage: false, + spago: false, bundleOutput: 'output/bundle.js', bundleNamespace: 'PS', bundle: false, warnings: true, watch: false, - output: 'output', - src: [ - path.join('src', '**', '*.purs'), - ...defaultDeps - ] - } + output: outputPath, + src: [] + }, loaderOptions, { + src: srcOption + }); - this.cacheable && this.cacheable() + if (!CACHE_VAR.installed) { + debugVerbose('installing purs-loader with options: %O', options); - let cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { - rebuild: false, - deferred: [], - bundleModules: [], - warnings: [], - errors: [] - } + CACHE_VAR.installed = true; - if (options.pscPackage && options.src) { - options.src = options.src.concat(defaultDeps) // append psc-package-provided source paths with users' - } + const invalidCb = () => { + debugVerbose('invalidating loader CACHE_VAR'); - options = Object.assign(defaultOptions, options) - - if (!config.purescriptLoaderInstalled) { - config.purescriptLoaderInstalled = true - - // invalidate loader cache when bundle is marked as invalid (in watch mode) - this._compiler.plugin('invalid', () => { - debug('invalidating loader cache'); - - cache = config.purescriptLoaderCache = { + CACHE_VAR = { rebuild: options.pscIde, deferred: [], bundleModules: [], - ideServer: cache.ideServer, - psModuleMap: cache.psModuleMap, + ideServer: CACHE_VAR.ideServer, + psModuleMap: CACHE_VAR.psModuleMap, warnings: [], - errors: [] - } - }); + errors: [], + compilationStarted: false, + compilationFinished: false, + installed: CACHE_VAR.installed, + srcOption: [] + }; + } - // add psc warnings to webpack compilation warnings - this._compiler.plugin('after-compile', (compilation, callback) => { - cache.warnings.forEach(warning => { + // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode) + if(this._compiler.hooks){ + this._compiler.hooks.invalid.tap('purs-loader', invalidCb); + } else { + this._compiler.plugin('invalid', invalidCb); + } + + const afterCompileCb = (compilation, callback) => { + CACHE_VAR.warnings.forEach(warning => { compilation.warnings.push(warning); }); - cache.errors.forEach(error => { + CACHE_VAR.errors.forEach(error => { compilation.errors.push(error); }); callback() - }); + } + + // add psc warnings to webpack compilation warnings + if(this._compiler.hooks) { + this._compiler.hooks.afterCompile.tapAsync('purs-loader', afterCompileCb); + } else { + this._compiler.plugin('after-compile', afterCompileCb); + } } - const psModuleName = PsModuleMap.match(source) + const psModuleName = PsModuleMap.matchModule(source); + 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); + CACHE_VAR.errors.push(error); } } } - debug('loader called', psModule.name) + debug('loading %s', psModule.name); if (options.bundle) { - cache.bundleModules.push(psModule.name) + CACHE_VAR.bundleModules.push(psModule.name); } - if (cache.rebuild) { - return PscIde.connect(psModule) - .then(PscIde.rebuild) - .then(toJavaScript) - .then(psModule.load) - .catch(psModule.reject) - } + if (CACHE_VAR.rebuild) { + const connect = () => { + if (!CACHE_VAR.ideServer) { + CACHE_VAR.ideServer = true; - if (cache.compilationFinished) { - return toJavaScript(psModule).then(psModule.load).catch(psModule.reject) - } + return ide.connect(psModule) + .then(ideServer => { + CACHE_VAR.ideServer = ideServer; + return psModule; + }) + .then(ide.loadWithRetry) + .catch(error => { + if (CACHE_VAR.ideServer.kill) { + debug('ide failed to initially load modules, stopping the ide server process'); - // We need to wait for compilation to finish before the loaders run so that - // references to compiled output are valid. - cache.deferred.push(psModule) - - if (!cache.compilationStarted) { - return Psc.compile(psModule) - .then(() => PsModuleMap.makeMap(options.src).then(map => { - debug('rebuilt module map after compile'); - cache.psModuleMap = map; - })) - .then(() => Promise.map(cache.deferred, psModule => { - if (typeof cache.ideServer === 'object') cache.ideServer.kill() - return toJavaScript(psModule).then(psModule.load) - })) + CACHE_VAR.ideServer.kill(); + } + + CACHE_VAR.ideServer = null; + + return Promise.reject(error); + }) + ; + } + else { + return Promise.resolve(psModule); + } + }; + + const rebuild = () => + ide.rebuild(psModule) + .then(() => + toJavaScript(psModule) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) + .catch(psModule.reject) + ) .catch(error => { - cache.deferred[0].reject(error) - cache.deferred.slice(1).forEach(psModule => psModule.reject(new Error('purs-loader failed'))) + 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(() => { + CACHE_VAR.compilationFinished = true; + }) + .then(() => + Promise.map(CACHE_VAR.deferred, psModule => + ide.load(psModule) + .then(() => toJavaScript(psModule)) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) + ) + ) + .catch(error => { + CACHE_VAR.deferred[0].reject(error); + + CACHE_VAR.deferred.slice(1).forEach(psModule => { + psModule.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(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_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(() => { + CACHE_VAR.compilationFinished = true; + }) + .then(() => { + if (options.bundle) { + return bundle(options, CACHE_VAR.bundleModules); + } + }) + .then(() => + Promise.map(CACHE_VAR.deferred, psModule => + toJavaScript(psModule) + .then(js => sourceMaps(psModule, js)) + .then(psModule.load) + ) + ) + .catch(error => { + CACHE_VAR.deferred[0].reject(error); + + CACHE_VAR.deferred.slice(1).forEach(psModule => { + psModule.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. + } } }