From 1c12889c0adf91cf3116a9d5ff44b7466b1dfcc9 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sat, 22 Apr 2017 10:52:54 -0400 Subject: Support for PureScript 0.11 Resolves #89 --- src/PsModuleMap.js | 78 --------------- src/Psc.js | 108 -------------------- src/PscIde.js | 262 ------------------------------------------------- src/bundle.js | 59 +++++++++++ src/compile.js | 58 +++++++++++ src/ide.js | 260 ++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 48 ++++++--- src/purs-module-map.js | 74 ++++++++++++++ 8 files changed, 486 insertions(+), 461 deletions(-) delete mode 100644 src/PsModuleMap.js delete mode 100644 src/Psc.js delete mode 100644 src/PscIde.js create mode 100644 src/bundle.js create mode 100644 src/compile.js create mode 100644 src/ide.js create mode 100644 src/purs-module-map.js (limited to 'src') diff --git a/src/PsModuleMap.js b/src/PsModuleMap.js deleted file mode 100644 index 0ae687c..0000000 --- a/src/PsModuleMap.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -const path = require('path'); - -const Promise = require('bluebird'); - -const fs = Promise.promisifyAll(require('fs')); - -const globby = require('globby'); - -const debug = require('debug')('purs-loader'); - -const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i; - -const importModuleRegex = /(?:^|\n)\s*import\s+([\w\.]+)/ig; - -function matchModule(str) { - const matches = str.match(srcModuleRegex); - return matches && matches[1]; -} -module.exports.match = matchModule; - -function matchImports(str) { - const matches = str.match(importModuleRegex); - return (matches || []).map(a => a.replace(/\n?\s*import\s+/i, '')); -} -module.exports.matchImports = matchImports; - -function makeMapEntry(filePurs) { - const dirname = path.dirname(filePurs); - - const basename = path.basename(filePurs, '.purs'); - - const fileJs = path.join(dirname, `${basename}.js`); - - const result = Promise.props({ - filePurs: fs.readFileAsync(filePurs, 'utf8'), - fileJs: fs.readFileAsync(fileJs, 'utf8').catch(() => undefined) - }).then(fileMap => { - const sourcePurs = fileMap.filePurs; - - const sourceJs = fileMap.fileJs; - - const moduleName = matchModule(sourcePurs); - - const imports = matchImports(sourcePurs); - - const map = {}; - - map[moduleName] = map[moduleName] || {}; - - map[moduleName].src = path.resolve(filePurs); - - map[moduleName].imports = imports; - - if (sourceJs) { - map[moduleName].ffi = path.resolve(fileJs); - } - - return map; - }); - - return result; -} -module.exports.makeMapEntry = makeMapEntry; - -function makeMap(src) { - debug('loading PureScript source and FFI files from %o', src); - - const globs = [].concat(src); - - return globby(globs).then(paths => - Promise.all(paths.map(makeMapEntry)).then(result => - result.reduce(Object.assign, {}) - ) - ); -} -module.exports.makeMap = makeMap; diff --git a/src/Psc.js b/src/Psc.js deleted file mode 100644 index ffa32b7..0000000 --- a/src/Psc.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -const path = require('path'); - -const Promise = require('bluebird') - -const fs = Promise.promisifyAll(require('fs')) - -const spawn = require('cross-spawn') - -const debug = require('debug')('purs-loader'); - -const dargs = require('./dargs'); - -function compile(psModule) { - const options = psModule.options - const cache = psModule.cache - const stderr = [] - - if (cache.compilationStarted) return Promise.resolve(psModule) - - cache.compilationStarted = true - - const args = dargs(Object.assign({ - _: options.src, - output: options.output, - }, options.pscArgs)) - - debug('spawning compiler %s %o', options.psc, args) - - return (new Promise((resolve, reject) => { - debug('compiling PureScript...') - - const compilation = spawn(options.psc, args) - - compilation.stderr.on('data', data => { - stderr.push(data.toString()); - }); - - compilation.on('close', code => { - debug('finished compiling PureScript.') - cache.compilationFinished = true - if (code !== 0) { - const errorMessage = stderr.join(''); - if (errorMessage.length) { - psModule.emitError(errorMessage); - } - if (options.watch) { - resolve(psModule); - } - else { - reject(new Error('compilation failed')) - } - } else { - const warningMessage = stderr.join(''); - if (options.warnings && warningMessage.length) { - psModule.emitWarning(warningMessage); - } - resolve(psModule) - } - }) - })) - .then(compilerOutput => { - if (options.bundle) { - return bundle(options, cache).then(() => psModule) - } - return psModule - }) -} -module.exports.compile = compile; - -function bundle(options, cache) { - if (cache.bundle) return Promise.resolve(cache.bundle) - - const stdout = [] - const stderr = cache.bundle = [] - - const args = dargs(Object.assign({ - _: [path.join(options.output, '*', '*.js')], - output: options.bundleOutput, - namespace: options.bundleNamespace, - }, options.pscBundleArgs)) - - cache.bundleModules.forEach(name => args.push('--module', name)) - - debug('spawning bundler %s %o', options.pscBundle, args.join(' ')) - - return (new Promise((resolve, reject) => { - debug('bundling PureScript...') - - const compilation = spawn(options.pscBundle, args) - - compilation.stdout.on('data', data => stdout.push(data.toString())) - compilation.stderr.on('data', data => stderr.push(data.toString())) - compilation.on('close', code => { - debug('finished bundling PureScript.') - if (code !== 0) { - const errorMessage = stderr.join(''); - if (errorMessage.length) { - psModule.emitError(errorMessage); - } - return reject(new Error('bundling failed')) - } - cache.bundle = stderr - resolve(fs.appendFileAsync(options.bundleOutput, `module.exports = ${options.bundleNamespace}`)) - }) - })) -} diff --git a/src/PscIde.js b/src/PscIde.js deleted file mode 100644 index bf92b38..0000000 --- a/src/PscIde.js +++ /dev/null @@ -1,262 +0,0 @@ -'use strict'; - -const path = require('path'); - -const Promise = require('bluebird'); - -const fs = Promise.promisifyAll(require('fs')); - -const retryPromise = require('promise-retry'); - -const spawn = require('cross-spawn'); - -const colors = require('chalk'); - -const debug = require('debug')('purs-loader'); - -const dargs = require('./dargs'); - -const Psc = require('./Psc'); - -const PsModuleMap = require('./PsModuleMap'); - -function connect(psModule) { - const options = psModule.options - const cache = psModule.cache - - if (cache.ideServer) return Promise.resolve(psModule) - - cache.ideServer = true - - const connect_ = () => new Promise((resolve, reject) => { - const args = dargs(options.pscIdeArgs) - - debug('attempting to connect to psc-ide-server', args) - - const ideClient = spawn('psc-ide-client', args) - - ideClient.stderr.on('data', data => { - debug(data.toString()) - cache.ideServer = false - reject(new Error('psc-ide-client failed')) - }) - ideClient.stdout.once('data', data => { - debug(data.toString()) - if (data.toString()[0] === '{') { - const res = JSON.parse(data.toString()) - if (res.resultType === 'success') { - cache.ideServer = ideServer - resolve(psModule) - } else { - cache.ideServer = ideServer - reject(new Error('psc-ide-client failed')) - } - } else { - cache.ideServer = false - reject(new Error('psc-ide-client failed')) - } - }) - ideClient.stdin.resume() - ideClient.stdin.write(JSON.stringify({ command: 'load' })) - ideClient.stdin.write('\n') - }) - - const serverArgs = dargs(Object.assign({ - outputDirectory: options.output, - '_': options.src - }, options.pscIdeServerArgs)) - - debug('attempting to start psc-ide-server', serverArgs) - - const ideServer = cache.ideServer = spawn('psc-ide-server', serverArgs) - - ideServer.stdout.on('data', data => { - debug('psc-ide-server stdout: %s', data.toString()); - }); - - ideServer.stderr.on('data', data => { - debug('psc-ide-server stderr: %s', data.toString()); - }); - - ideServer.on('error', error => { - debug('psc-ide-server error: %o', error); - }); - - ideServer.on('close', (code, signal) => { - debug('psc-ide-server close: %s %s', code, signal); - }); - - return retryPromise((retry, number) => { - return connect_().catch(error => { - if (!cache.ideServer && number === 9) { - debug(error) - - console.warn('Failed to connect to or start psc-ide-server. A full compilation will occur on rebuild'); - - return Promise.resolve(psModule) - } - - return retry(error) - }) - }, { - retries: 9, - factor: 1, - minTimeout: 333, - maxTimeout: 333, - }) -} -module.exports.connect = connect; - -function rebuild(psModule) { - const options = psModule.options - const cache = psModule.cache - - debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) - - const request = (body) => new Promise((resolve, reject) => { - const args = dargs(options.pscIdeArgs) - const ideClient = spawn('psc-ide-client', args) - - var stdout = '' - var stderr = '' - - ideClient.stdout.on('data', data => { - stdout = stdout + data.toString() - }) - - ideClient.stderr.on('data', data => { - stderr = stderr + data.toString() - }) - - ideClient.on('close', code => { - if (code !== 0) { - const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr - return reject(new Error(error)) - } - - let res = null - - try { - res = JSON.parse(stdout.toString()) - debug(res) - } catch (err) { - return reject(err) - } - - if (res && !Array.isArray(res.result)) { - return resolve(psModule); - } - - Promise.map(res.result, (item, i) => { - debug(item) - return formatIdeResult(item, options, i, res.result.length) - }) - .then(compileMessages => { - if (res.resultType === 'error') { - if (res.result.some(item => { - const isModuleNotFound = item.errorCode === 'ModuleNotFound'; - - const isUnknownModule = item.errorCode === 'UnknownModule'; - - const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); - - return isModuleNotFound || isUnknownModule || isUnknownModuleImport; - })) { - debug('unknown module, attempting full recompile') - return Psc.compile(psModule) - .then(() => PsModuleMap.makeMap(options.src).then(map => { - debug('rebuilt module map after unknown module forced a recompile'); - cache.psModuleMap = map; - })) - .then(() => request({ command: 'load' })) - .then(resolve) - .catch(() => resolve(psModule)) - } - const errorMessage = compileMessages.join('\n'); - if (errorMessage.length) { - psModule.emitError(errorMessage); - } - resolve(psModule); - } else { - const warningMessage = compileMessages.join('\n'); - if (options.warnings && warningMessage.length) { - psModule.emitWarning(warningMessage); - } - resolve(psModule); - } - }) - }) - - debug('psc-ide-client stdin: %o', body); - - ideClient.stdin.write(JSON.stringify(body)) - ideClient.stdin.write('\n') - }) - - return request({ - command: 'rebuild', - params: { - file: psModule.srcPath, - } - }) -} -module.exports.rebuild = rebuild; - -function formatIdeResult(result, options, index, length) { - let numAndErr = `[${index+1}/${length} ${result.errorCode}]` - numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr - - function makeResult() { - return Promise.resolve(`\n${numAndErr} ${result.message}`) - } - - function makeResultSnippet(filename, pos) { - const srcPath = path.relative(options.context, filename); - const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` - - return fs.readFileAsync(filename, 'utf8').then(source => { - const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) - const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine - const up = options.pscIdeColors ? colors.red('^') : '^' - const down = options.pscIdeColors ? colors.red('v') : 'v' - let trimmed = lines.slice(0) - - if (endsOnNewline) { - lines.splice(lines.length - 1, 1) - pos.endLine = pos.endLine - 1 - pos.endColumn = lines[lines.length - 1].length || 1 - } - - // strip newlines at the end - if (endsOnNewline) { - trimmed = lines.reverse().reduce((trimmed, line, i) => { - if (i === 0 && line === '') trimmed.trimming = true - if (!trimmed.trimming) trimmed.push(line) - if (trimmed.trimming && line !== '') { - trimmed.trimming = false - trimmed.push(line) - } - return trimmed - }, []).reverse() - pos.endLine = pos.endLine - (lines.length - trimmed.length) - pos.endColumn = trimmed[trimmed.length - 1].length || 1 - } - - const spaces = ' '.repeat(String(pos.endLine).length) - let snippet = trimmed.map((line, i) => { - return ` ${pos.startLine + i} ${line}` - }).join('\n') - - if (trimmed.length === 1) { - snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` - } else { - snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` - snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` - } - - return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) - }) - } - - return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); -} diff --git a/src/bundle.js b/src/bundle.js new file mode 100644 index 0000000..6627ffe --- /dev/null +++ b/src/bundle.js @@ -0,0 +1,59 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird') + +const fs = Promise.promisifyAll(require('fs')) + +const spawn = require('cross-spawn') + +const debug = require('debug')('purs-loader'); + +const dargs = require('./dargs'); + +module.exports = function bundle(options, cache) { + if (cache.bundle) return Promise.resolve(cache.bundle) + + const stdout = [] + + const stderr = cache.bundle = [] + + const bundleCommand = options.pscBundle || 'purs'; + + const bundleArgs = (options.pscBundle ? [] : [ 'bundle' ]).concat(dargs(Object.assign({ + _: [path.join(options.output, '*', '*.js')], + output: options.bundleOutput, + namespace: options.bundleNamespace, + }, options.pscBundleArgs))); + + cache.bundleModules.forEach(name => bundleArgs.push('--module', name)) + + debug('spawning bundler %s %o', bundleCommand, bundleArgs); + + return (new Promise((resolve, reject) => { + debug('bundling PureScript...') + + const compilation = spawn(bundleCommand, bundleArgs) + + compilation.stdout.on('data', data => stdout.push(data.toString())) + + compilation.stderr.on('data', data => stderr.push(data.toString())) + + compilation.on('close', code => { + debug('finished bundling PureScript.') + + if (code !== 0) { + const errorMessage = stderr.join(''); + if (errorMessage.length) { + psModule.emitError(errorMessage); + } + return reject(new Error('bundling failed')) + } + + cache.bundle = stderr + + resolve(fs.appendFileAsync(options.bundleOutput, `module.exports = ${options.bundleNamespace}`)) + }) + })) +}; diff --git a/src/compile.js b/src/compile.js new file mode 100644 index 0000000..8b5d87f --- /dev/null +++ b/src/compile.js @@ -0,0 +1,58 @@ +'use strict'; + +const Promise = require('bluebird'); + +const spawn = require('cross-spawn'); + +const debug = require('debug')('purs-loader'); + +const dargs = require('./dargs'); + +module.exports = function compile(psModule) { + const options = psModule.options + + const cache = psModule.cache + + const stderr = [] + + const compileCommand = options.psc || 'purs'; + + const compileArgs = (options.psc ? [] : [ 'compile' ]).concat(dargs(Object.assign({ + _: options.src, + output: options.output, + }, options.pscArgs))) + + debug('spawning compiler %s %o', compileCommand, compileArgs) + + return new Promise((resolve, reject) => { + debug('compiling PureScript...') + + const compilation = spawn(compileCommand, compileArgs) + + compilation.stderr.on('data', data => { + stderr.push(data.toString()); + }); + + compilation.on('close', code => { + debug('finished compiling PureScript.') + if (code !== 0) { + const errorMessage = stderr.join(''); + if (errorMessage.length) { + psModule.emitError(errorMessage); + } + if (options.watch) { + resolve(psModule); + } + else { + reject(new Error('compilation failed')) + } + } else { + const warningMessage = stderr.join(''); + if (options.warnings && warningMessage.length) { + psModule.emitWarning(warningMessage); + } + resolve(psModule) + } + }) + }); +}; diff --git a/src/ide.js b/src/ide.js new file mode 100644 index 0000000..f839fd5 --- /dev/null +++ b/src/ide.js @@ -0,0 +1,260 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird'); + +const fs = Promise.promisifyAll(require('fs')); + +const retryPromise = require('promise-retry'); + +const spawn = require('cross-spawn'); + +const colors = require('chalk'); + +const debug = require('debug')('purs-loader'); + +const dargs = require('./dargs'); + +const Psc = require('./Psc'); + +const PsModuleMap = require('./PsModuleMap'); + +module.exports.connect = function connect(psModule) { + const options = psModule.options + const cache = psModule.cache + + if (cache.ideServer) return Promise.resolve(psModule) + + cache.ideServer = true + + const connect_ = () => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + + debug('attempting to run purs ide client: %o', args) + + const ideClient = spawn('purs', ['ide', 'client'].concat(args)) + + ideClient.stderr.on('data', data => { + debug(data.toString()) + cache.ideServer = false + reject(new Error('purs ide client failed')) + }) + ideClient.stdout.once('data', data => { + debug(data.toString()) + if (data.toString()[0] === '{') { + const res = JSON.parse(data.toString()) + if (res.resultType === 'success') { + cache.ideServer = ideServer + resolve(psModule) + } else { + cache.ideServer = ideServer + reject(new Error('purs ide client failed')) + } + } else { + cache.ideServer = false + reject(new Error('purs ide client failed')) + } + }) + ideClient.stdin.resume() + ideClient.stdin.write(JSON.stringify({ command: 'load' })) + ideClient.stdin.write('\n') + }) + + const serverArgs = dargs(Object.assign({ + outputDirectory: options.output, + '_': options.src + }, options.pscIdeServerArgs)) + + debug('attempting to start purs ide server: %o', serverArgs) + + const ideServer = cache.ideServer = spawn('purs', ['ide', 'server'].concat(serverArgs)) + + ideServer.stdout.on('data', data => { + debug('purs ide server stdout: %s', data.toString()); + }); + + ideServer.stderr.on('data', data => { + debug('purs ide server stderr: %s', data.toString()); + }); + + ideServer.on('error', error => { + debug('purs ide server error: %o', error); + }); + + ideServer.on('close', (code, signal) => { + debug('purs ide server close: %s %s', code, signal); + }); + + return retryPromise((retry, number) => { + return connect_().catch(error => { + if (!cache.ideServer && number === 9) { + debug(error) + + console.warn('Failed to connect to or start purs ide server. A full compilation will occur on rebuild'); + + return Promise.resolve(psModule) + } + + return retry(error) + }) + }, { + retries: 9, + factor: 1, + minTimeout: 333, + maxTimeout: 333, + }) +}; + +module.exports.rebuild = function rebuild(psModule) { + const options = psModule.options + const cache = psModule.cache + + debug('attempting rebuild with purs ide client %s', psModule.srcPath) + + const request = (body) => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + const ideClient = spawn('purs', ['ide', 'client'].concat(args)) + + var stdout = '' + var stderr = '' + + ideClient.stdout.on('data', data => { + stdout = stdout + data.toString() + }) + + ideClient.stderr.on('data', data => { + stderr = stderr + data.toString() + }) + + ideClient.on('close', code => { + if (code !== 0) { + const error = stderr === '' ? 'Failed to spawn purs ide client' : stderr + return reject(new Error(error)) + } + + let res = null + + try { + res = JSON.parse(stdout.toString()) + debug(res) + } catch (err) { + return reject(err) + } + + if (res && !Array.isArray(res.result)) { + return resolve(psModule); + } + + Promise.map(res.result, (item, i) => { + debug(item) + return formatIdeResult(item, options, i, res.result.length) + }) + .then(compileMessages => { + if (res.resultType === 'error') { + if (res.result.some(item => { + const isModuleNotFound = item.errorCode === 'ModuleNotFound'; + + const isUnknownModule = item.errorCode === 'UnknownModule'; + + const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); + + return isModuleNotFound || isUnknownModule || isUnknownModuleImport; + })) { + debug('unknown module, attempting full recompile') + return Psc.compile(psModule) + .then(() => PsModuleMap.makeMap(options.src).then(map => { + debug('rebuilt module map after unknown module forced a recompile'); + cache.psModuleMap = map; + })) + .then(() => request({ command: 'load' })) + .then(resolve) + .catch(() => resolve(psModule)) + } + const errorMessage = compileMessages.join('\n'); + if (errorMessage.length) { + psModule.emitError(errorMessage); + } + resolve(psModule); + } else { + const warningMessage = compileMessages.join('\n'); + if (options.warnings && warningMessage.length) { + psModule.emitWarning(warningMessage); + } + resolve(psModule); + } + }) + }) + + debug('purs ide client stdin: %o', body); + + ideClient.stdin.write(JSON.stringify(body)) + ideClient.stdin.write('\n') + }) + + return request({ + command: 'rebuild', + params: { + file: psModule.srcPath, + } + }) +}; + +function formatIdeResult(result, options, index, length) { + let numAndErr = `[${index+1}/${length} ${result.errorCode}]` + numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr + + function makeResult() { + return Promise.resolve(`\n${numAndErr} ${result.message}`) + } + + function makeResultSnippet(filename, pos) { + const srcPath = path.relative(options.context, filename); + const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` + + return fs.readFileAsync(filename, 'utf8').then(source => { + const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) + const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine + const up = options.pscIdeColors ? colors.red('^') : '^' + const down = options.pscIdeColors ? colors.red('v') : 'v' + let trimmed = lines.slice(0) + + if (endsOnNewline) { + lines.splice(lines.length - 1, 1) + pos.endLine = pos.endLine - 1 + pos.endColumn = lines[lines.length - 1].length || 1 + } + + // strip newlines at the end + if (endsOnNewline) { + trimmed = lines.reverse().reduce((trimmed, line, i) => { + if (i === 0 && line === '') trimmed.trimming = true + if (!trimmed.trimming) trimmed.push(line) + if (trimmed.trimming && line !== '') { + trimmed.trimming = false + trimmed.push(line) + } + return trimmed + }, []).reverse() + pos.endLine = pos.endLine - (lines.length - trimmed.length) + pos.endColumn = trimmed[trimmed.length - 1].length || 1 + } + + const spaces = ' '.repeat(String(pos.endLine).length) + let snippet = trimmed.map((line, i) => { + return ` ${pos.startLine + i} ${line}` + }).join('\n') + + if (trimmed.length === 1) { + snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` + } else { + snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` + snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` + } + + return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) + }) + } + + return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); +} diff --git a/src/index.js b/src/index.js index 047927c..799f8f9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,27 @@ 'use strict' const debug = require('debug')('purs-loader') + 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 spawn = require('cross-spawn').sync + const eol = require('os').EOL module.exports = function purescriptLoader(source, map) { @@ -34,9 +46,9 @@ module.exports = function purescriptLoader(source, map) { const defaultDeps = depsPaths(options.pscPackage) const defaultOptions = { context: config.context, - psc: 'psc', + psc: null, pscArgs: {}, - pscBundle: 'psc-bundle', + pscBundle: null, pscBundleArgs: {}, pscIde: false, pscIdeColors: options.psc === 'psa', @@ -102,7 +114,7 @@ module.exports = function purescriptLoader(source, map) { }); } - const psModuleName = PsModuleMap.match(source) + const psModuleName = PsModuleMap.matchModule(source) const psModule = { name: psModuleName, load: js => callback(null, js), @@ -131,8 +143,8 @@ module.exports = function purescriptLoader(source, map) { } if (cache.rebuild) { - return PscIde.connect(psModule) - .then(PscIde.rebuild) + return ide.connect(psModule) + .then(ide.rebuild) .then(toJavaScript) .then(psModule.load) .catch(psModule.reject) @@ -147,11 +159,21 @@ module.exports = function purescriptLoader(source, map) { 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; - })) + cache.compilationStarted = true; + + return compile(psModule) + .then(() => { + cache.compilationFinished = true; + + const bundlePromise = options.bundle ? bundle(options, cache) : Promise.resolve(); + + return bundlePromise.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) diff --git a/src/purs-module-map.js b/src/purs-module-map.js new file mode 100644 index 0000000..b906d08 --- /dev/null +++ b/src/purs-module-map.js @@ -0,0 +1,74 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird'); + +const fs = Promise.promisifyAll(require('fs')); + +const globby = require('globby'); + +const debug = require('debug')('purs-loader'); + +const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i; + +const importModuleRegex = /(?:^|\n)\s*import\s+([\w\.]+)/ig; + +module.exports.matchModule = function matchModule(str) { + const matches = str.match(srcModuleRegex); + return matches && matches[1]; +}; + +module.exports.matchImports = function matchImports(str) { + const matches = str.match(importModuleRegex); + return (matches || []).map(a => a.replace(/\n?\s*import\s+/i, '')); +}; + +module.exports.makeMapEntry = function makeMapEntry(filePurs) { + const dirname = path.dirname(filePurs); + + const basename = path.basename(filePurs, '.purs'); + + const fileJs = path.join(dirname, `${basename}.js`); + + const result = Promise.props({ + filePurs: fs.readFileAsync(filePurs, 'utf8'), + fileJs: fs.readFileAsync(fileJs, 'utf8').catch(() => undefined) + }).then(fileMap => { + const sourcePurs = fileMap.filePurs; + + const sourceJs = fileMap.fileJs; + + const moduleName = module.exports.matchModule(sourcePurs); + + const imports = module.exports.matchImports(sourcePurs); + + const map = {}; + + map[moduleName] = map[moduleName] || {}; + + map[moduleName].src = path.resolve(filePurs); + + map[moduleName].imports = imports; + + if (sourceJs) { + map[moduleName].ffi = path.resolve(fileJs); + } + + return map; + }); + + return result; +}; + +module.exports.makeMap = function makeMap(src) { + debug('loading PureScript source and FFI files from %o', src); + + const globs = [].concat(src); + + return globby(globs).then(paths => + Promise.all(paths.map(module.exports.makeMapEntry)).then(result => + result.reduce(Object.assign, {}) + ) + ); +}; -- cgit v1.2.3