]> 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 9db8a6337a0afe31d0f02e32b6de48bda168e4e3..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) {
@@ -65,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),
@@ -89,6 +91,8 @@ module.exports = function purescriptLoader(source, map) {
     cache: cache,
   }
 
+  debug('loader called', psModule.name)
+
   if (options.bundle) {
     cache.bundleModules.push(psModule.name)
   }
@@ -101,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)
   }
 
@@ -109,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()
@@ -129,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
@@ -165,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,
@@ -185,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)
       }
     })
@@ -216,11 +217,27 @@ function rebuild(psModule) {
     const args = dargs(options.pscIdeArgs)
     const ideClient = spawn('psc-ide-client', args)
 
-    ideClient.stdout.once('data', data => {
+    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(data.toString())
+        res = JSON.parse(stdout.toString())
         debug(res)
       } catch (err) {
         return reject(err)
@@ -245,17 +262,15 @@ function rebuild(psModule) {
               .then(resolve)
               .catch(() => reject('psc-ide rebuild failed'))
           }
-          cache.errors = compileMessages
+          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')
   })
@@ -346,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
@@ -356,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
@@ -466,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'))
   }, [])
 }