]> git.immae.eu Git - github/fretlink/purs-loader.git/blobdiff - src/index.js
Escape require paths for windows
[github/fretlink/purs-loader.git] / src / index.js
index 17951d3b04e82a49e1f3a789b09e8666a41d8a69..cd85c89a9a57d9364547b485e1e0e39b93fbb1b2 100644 (file)
@@ -6,11 +6,13 @@ const loaderUtils = require('loader-utils')
 const globby = require('globby')
 const Promise = require('bluebird')
 const fs = Promise.promisifyAll(require('fs'))
-const spawn = require('child_process').spawn
+const spawn = require('cross-spawn')
 const path = require('path')
 const retryPromise = require('promise-retry')
+const jsStringEscape = require('js-string-escape')
 
-const psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i
+const ffiModuleRegex = /\/\/\s+module\s+([\w\.]+)/i
+const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i
 const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g
 
 module.exports = function purescriptLoader(source, map) {
@@ -25,6 +27,7 @@ module.exports = function purescriptLoader(source, map) {
     pscArgs: {},
     pscBundle: 'psc-bundle',
     pscBundleArgs: {},
+    pscIde: false,
     pscIdeColors: webpackOptions.psc === 'psa' || query.psc === 'psa',
     pscIdeArgs: {},
     bundleOutput: 'output/bundle.js',
@@ -56,7 +59,7 @@ module.exports = function purescriptLoader(source, map) {
     // invalidate loader cache when bundle is marked as invalid (in watch mode)
     this._compiler.plugin('invalid', () => {
       cache = config.purescriptLoaderCache = {
-        rebuild: true,
+        rebuild: options.pscIde,
         deferred: [],
         ideServer: cache.ideServer
       }
@@ -64,19 +67,19 @@ module.exports = function purescriptLoader(source, map) {
 
     // add psc warnings to webpack compilation warnings
     this._compiler.plugin('after-compile', (compilation, callback) => {
-      if (options.warnings && cache.warnings && cache.warnings.length) {
-        compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings.join('')}`)
+      if (options.warnings && cache.warnings) {
+        compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings}`)
       }
 
-      if (cache.errors && cache.errors.length) {
-        compilation.errors.unshift(`PureScript compilation:\n${cache.errors.join('\n')}`)
+      if (cache.errors) {
+        compilation.errors.unshift(`PureScript compilation:\n${cache.errors}`)
       }
 
       callback()
     })
   }
 
-  const psModuleName = match(psModuleRegex, source)
+  const psModuleName = match(srcModuleRegex, source)
   const psModule = {
     name: psModuleName,
     load: js => callback(null, js),
@@ -88,6 +91,8 @@ module.exports = function purescriptLoader(source, map) {
     cache: cache,
   }
 
+  debug('loader called', psModule.name)
+
   if (options.bundle) {
     cache.bundleModules.push(psModule.name)
   }
@@ -100,7 +105,7 @@ module.exports = function purescriptLoader(source, map) {
       .catch(psModule.reject)
   }
 
-  if (cache.compilation && cache.compilation.length) {
+  if (cache.compilationFinished) {
     return toJavaScript(psModule).then(psModule.load).catch(psModule.reject)
   }
 
@@ -108,7 +113,7 @@ module.exports = function purescriptLoader(source, map) {
   // references to compiled output are valid.
   cache.deferred.push(psModule)
 
-  if (!cache.compilation) {
+  if (!cache.compilationStarted) {
     return compile(psModule)
       .then(() => Promise.map(cache.deferred, psModule => {
         if (typeof cache.ideServer === 'object') cache.ideServer.kill()
@@ -128,31 +133,29 @@ function toJavaScript(psModule) {
   const bundlePath = path.resolve(options.bundleOutput)
   const jsPath = cache.bundle ? bundlePath : psModule.jsPath
 
-  debug('loading JavaScript for', psModule.srcPath)
+  debug('loading JavaScript for', psModule.name)
 
   return Promise.props({
     js: fs.readFileAsync(jsPath, 'utf8'),
-    psModuleMap: psModuleMap(options.src, cache)
+    psModuleMap: psModuleMap(options, cache)
   }).then(result => {
     let js = ''
 
     if (options.bundle) {
       // if bundling, return a reference to the bundle
       js = 'module.exports = require("'
-             + path.relative(psModule.srcDir, options.bundleOutput)
+             + jsStringEscape(path.relative(psModule.srcDir, options.bundleOutput))
              + '")["' + psModule.name + '"]'
     } else {
       // replace require paths to output files generated by psc with paths
       // to purescript sources, which are then also run through this loader.
-      const foreignRequire = 'require("' + path.resolve(
-        path.join(psModule.options.output, psModule.name, 'foreign.js')
-      ) + '")'
-
       js = result.js
         .replace(requireRegex, (m, p1) => {
-          return 'require("' + result.psModuleMap[p1] + '")'
+          return 'require("' + jsStringEscape(result.psModuleMap[p1].src) + '")'
+        })
+        .replace(/require\(['"]\.\/foreign['"]\)/g, (m, p1) => {
+          return 'require("' + jsStringEscape(result.psModuleMap[psModule.name].ffi) + '")'
         })
-        .replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire)
     }
 
     return js
@@ -164,12 +167,9 @@ function compile(psModule) {
   const cache = psModule.cache
   const stderr = []
 
-  if (cache.compilation) return Promise.resolve(cache.compilation)
-
-  cache.compilation = []
-  cache.warnings = []
-  cache.errors = []
+  if (cache.compilationStarted) return Promise.resolve(psModule)
 
+  cache.compilationStarted = true
 
   const args = dargs(Object.assign({
     _: options.src,
@@ -184,15 +184,17 @@ function compile(psModule) {
 
     const compilation = spawn(options.psc, args)
 
+    compilation.stdout.on('data', data => stderr.push(data.toString()))
     compilation.stderr.on('data', data => stderr.push(data.toString()))
 
     compilation.on('close', code => {
       console.log('Finished compiling PureScript.')
+      cache.compilationFinished = true
       if (code !== 0) {
-        cache.compilation = cache.errors = stderr
+        cache.errors = stderr.join('')
         reject(true)
       } else {
-        cache.compilation = cache.warnings = stderr
+        cache.warnings = stderr.join('')
         resolve(psModule)
       }
     })
@@ -215,14 +217,36 @@ function rebuild(psModule) {
     const args = dargs(options.pscIdeArgs)
     const ideClient = spawn('psc-ide-client', args)
 
-    ideClient.stdout.once('data', data => {
-      const res = JSON.parse(data.toString())
-      debug(res)
+    var stdout = ''
+    var stderr = ''
+
+    ideClient.stdout.on('data', data => {
+      stdout = stdout + data.toString()
+    })
+
+    ideClient.stderr.on('data', data => {
+      stderr = stderr + data.toString()
+    })
 
-      if (!Array.isArray(res.result)) {
+    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 res.resultType === 'success'
                ? resolve(psModule)
-               : reject(res)
+               : reject('psc-ide rebuild failed')
       }
 
       Promise.map(res.result, (item, i) => {
@@ -231,17 +255,22 @@ function rebuild(psModule) {
       })
       .then(compileMessages => {
         if (res.resultType === 'error') {
-          cache.errors = compileMessages
-          reject(res)
+          if (res.result.some(item => item.errorCode === 'UnknownModule')) {
+            console.log('Unknown module, attempting full recompile')
+            return compile(psModule)
+              .then(() => request({ command: 'load' }))
+              .then(resolve)
+              .catch(() => reject('psc-ide rebuild failed'))
+          }
+          cache.errors = compileMessages.join('\n')
+          reject('psc-ide rebuild failed')
         } else {
-          cache.warnings = compileMessages
+          cache.warnings = compileMessages.join('\n')
           resolve(psModule)
         }
       })
     })
 
-    ideClient.stderr.once('data', data => reject(data.toString()))
-
     ideClient.stdin.write(JSON.stringify(body))
     ideClient.stdin.write('\n')
   })
@@ -251,14 +280,6 @@ function rebuild(psModule) {
     params: {
       file: psModule.srcPath,
     }
-  }).catch(res => {
-    if (res.resultType === 'error') {
-      if (res.result.some(item => item.errorCode === 'UnknownModule')) {
-        console.log('Unknown module, attempting full recompile')
-        return compile(psModule).then(() => request({ command: 'load' }))
-      }
-    }
-    return Promise.resolve(psModule)
   })
 }
 
@@ -340,7 +361,7 @@ function bundle(options, cache) {
     compilation.stderr.on('data', data => stderr.push(data.toString()))
     compilation.on('close', code => {
       if (code !== 0) {
-        cache.errors.concat(stderr)
+        cache.errors = (cache.errors || '') + stderr.join('')
         return reject(true)
       }
       cache.bundle = stderr
@@ -350,20 +371,30 @@ function bundle(options, cache) {
 }
 
 // map of PS module names to their source path
-function psModuleMap(globs, cache) {
+function psModuleMap(options, cache) {
   if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap)
 
+  const globs = [].concat(options.src).concat(options.ffi)
+
   return globby(globs).then(paths => {
     return Promise
       .props(paths.reduce((map, file) => {
         map[file] = fs.readFileAsync(file, 'utf8')
         return map
       }, {}))
-      .then(srcMap => {
-        cache.psModuleMap = Object.keys(srcMap).reduce((map, file) => {
-          const source = srcMap[file]
-          const psModuleName = match(psModuleRegex, source)
-          map[psModuleName] = path.resolve(file)
+      .then(fileMap => {
+        cache.psModuleMap = Object.keys(fileMap).reduce((map, file) => {
+          const source = fileMap[file]
+          const ext = path.extname(file)
+          const isPurs = ext.match(/purs$/i)
+          const moduleRegex = isPurs ? srcModuleRegex : ffiModuleRegex
+          const moduleName = match(moduleRegex, source)
+          map[moduleName] = map[moduleName] || {}
+          if (isPurs) {
+            map[moduleName].src = path.resolve(file)
+          } else {
+            map[moduleName].ffi = path.resolve(file)
+          }
           return map
         }, {})
         return cache.psModuleMap
@@ -460,6 +491,6 @@ function dargs(obj) {
     else if (Array.isArray(val)) val.forEach(v => args.push(arg, v))
     else args.push(arg, obj[key])
 
-    return args
+    return args.filter(arg => (typeof arg !== 'boolean'))
   }, [])
 }