3 var _typeof
= typeof Symbol
=== "function" && typeof Symbol
.iterator
=== "symbol" ? function (obj
) { return typeof obj
; } : function (obj
) { return obj
&& typeof Symbol
=== "function" && obj
.constructor === Symbol
? "symbol" : typeof obj
; };
5 var colors
= require('chalk');
6 var debug
= require('debug')('purs-loader');
7 var loaderUtils
= require('loader-utils');
8 var globby
= require('globby');
9 var Promise
= require('bluebird');
10 var fs
= Promise
.promisifyAll(require('fs'));
11 var spawn
= require('child_process').spawn
;
12 var path
= require('path');
13 var retryPromise
= require('promise-retry');
15 var psModuleRegex
= /(?:^|\n)module\s+([\w\.]+)/i;
16 var requireRegex
= /require\(['"]\.\.\/([\w\.]+)['"]\)/g;
18 module
.exports
= function purescriptLoader(source
, map
) {
19 var callback
= this.async();
20 var config
= this.options
;
21 var query
= loaderUtils
.parseQuery(this.query
);
22 var webpackOptions
= this.options
.purescriptLoader
|| {};
24 var options
= Object
.assign({
25 context: config
.context
,
28 pscBundle: 'psc-bundle',
31 pscIdeColors: webpackOptions
.psc
=== 'psa' || query
.psc
=== 'psa',
33 bundleOutput: 'output/bundle.js',
34 bundleNamespace: 'PS',
38 src: [path
.join('src', '**', '*.purs'), path
.join('bower_components', 'purescript-*', 'src', '**', '*.purs')],
39 ffi: [path
.join('src', '**', '*.js'), path
.join('bower_components', 'purescript-*', 'src', '**', '*.js')]
40 }, webpackOptions
, query
);
42 this.cacheable
&& this.cacheable();
44 var cache
= config
.purescriptLoaderCache
= config
.purescriptLoaderCache
|| {
50 if (!config
.purescriptLoaderInstalled
) {
51 config
.purescriptLoaderInstalled
= true;
53 // invalidate loader cache when bundle is marked as invalid (in watch mode)
54 this._compiler
.plugin('invalid', function () {
55 cache
= config
.purescriptLoaderCache
= {
56 rebuild: options
.pscIde
,
58 ideServer: cache
.ideServer
62 // add psc warnings to webpack compilation warnings
63 this._compiler
.plugin('after-compile', function (compilation
, callback
) {
64 if (options
.warnings
&& cache
.warnings
&& cache
.warnings
.length
) {
65 compilation
.warnings
.unshift('PureScript compilation:\n' + cache
.warnings
.join(''));
68 if (cache
.errors
&& cache
.errors
.length
) {
69 compilation
.errors
.unshift('PureScript compilation:\n' + cache
.errors
.join('\n'));
76 var psModuleName
= match(psModuleRegex
, source
);
79 load: function load(js
) {
80 return callback(null, js
);
82 reject: function reject(error
) {
83 return callback(error
);
85 srcPath: this.resourcePath
,
86 srcDir: path
.dirname(this.resourcePath
),
87 jsPath: path
.resolve(path
.join(options
.output
, psModuleName
, 'index.js')),
93 cache
.bundleModules
.push(psModule
.name
);
97 return connectIdeServer(psModule
).then(rebuild
).then(toJavaScript
).then(psModule
.load
).catch(psModule
.reject
);
100 if (cache
.compilation
&& cache
.compilation
.length
) {
101 return toJavaScript(psModule
).then(psModule
.load
).catch(psModule
.reject
);
104 // We need to wait for compilation to finish before the loaders run so that
105 // references to compiled output are valid.
106 cache
.deferred
.push(psModule
);
108 if (!cache
.compilation
) {
109 return compile(psModule
).then(function () {
110 return Promise
.map(cache
.deferred
, function (psModule
) {
111 if (_typeof(cache
.ideServer
) === 'object') cache
.ideServer
.kill();
112 return toJavaScript(psModule
).then(psModule
.load
);
114 }).catch(function (error
) {
115 cache
.deferred
[0].reject(error
);
116 cache
.deferred
.slice(1).forEach(function (psModule
) {
117 return psModule
.reject(true);
123 // The actual loader is executed *after* purescript compilation.
124 function toJavaScript(psModule
) {
125 var options
= psModule
.options
;
126 var cache
= psModule
.cache
;
127 var bundlePath
= path
.resolve(options
.bundleOutput
);
128 var jsPath
= cache
.bundle
? bundlePath : psModule
.jsPath
;
130 debug('loading JavaScript for', psModule
.srcPath
);
132 return Promise
.props({
133 js: fs
.readFileAsync(jsPath
, 'utf8'),
134 psModuleMap: psModuleMap(options
.src
, cache
)
135 }).then(function (result
) {
138 if (options
.bundle
) {
139 // if bundling, return a reference to the bundle
140 js
= 'module.exports = require("' + path
.relative(psModule
.srcDir
, options
.bundleOutput
) + '")["' + psModule
.name
+ '"]';
142 // replace require paths to output files generated by psc with paths
143 // to purescript sources, which are then also run through this loader.
144 var foreignRequire
= 'require("' + path
.resolve(path
.join(psModule
.options
.output
, psModule
.name
, 'foreign.js')) + '")';
146 js
= result
.js
.replace(requireRegex
, function (m
, p1
) {
147 return 'require("' + result
.psModuleMap
[p1
] + '")';
148 }).replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire
);
155 function compile(psModule
) {
156 var options
= psModule
.options
;
157 var cache
= psModule
.cache
;
160 if (cache
.compilation
) return Promise
.resolve(cache
.compilation
);
162 cache
.compilation
= [];
166 var args
= dargs(Object
.assign({
169 output: options
.output
170 }, options
.pscArgs
));
172 debug('spawning compiler %s %o', options
.psc
, args
);
174 return new Promise(function (resolve
, reject
) {
175 console
.log('\nCompiling PureScript...');
177 var compilation
= spawn(options
.psc
, args
);
179 compilation
.stderr
.on('data', function (data
) {
180 return stderr
.push(data
.toString());
183 compilation
.on('close', function (code
) {
184 console
.log('Finished compiling PureScript.');
186 cache
.compilation
= cache
.errors
= stderr
;
189 cache
.compilation
= cache
.warnings
= stderr
;
193 }).then(function (compilerOutput
) {
194 if (options
.bundle
) {
195 return bundle(options
, cache
).then(function () {
203 function rebuild(psModule
) {
204 var options
= psModule
.options
;
205 var cache
= psModule
.cache
;
207 debug('attempting rebuild with psc-ide-client %s', psModule
.srcPath
);
209 var request
= function request(body
) {
210 return new Promise(function (resolve
, reject
) {
211 var args
= dargs(options
.pscIdeArgs
);
212 var ideClient
= spawn('psc-ide-client', args
);
214 ideClient
.stdout
.once('data', function (data
) {
218 res
= JSON
.parse(data
.toString());
224 if (res
&& !Array
.isArray(res
.result
)) {
225 return res
.resultType
=== 'success' ? resolve(psModule
) : reject('psc-ide rebuild failed');
228 Promise
.map(res
.result
, function (item
, i
) {
230 return formatIdeResult(item
, options
, i
, res
.result
.length
);
231 }).then(function (compileMessages
) {
232 if (res
.resultType
=== 'error') {
233 if (res
.result
.some(function (item
) {
234 return item
.errorCode
=== 'UnknownModule';
236 console
.log('Unknown module, attempting full recompile');
237 return compile(psModule
).then(function () {
238 return request({ command: 'load' });
239 }).then(resolve
).catch(function () {
240 return reject('psc-ide rebuild failed');
243 cache
.errors
= compileMessages
;
244 reject('psc-ide rebuild failed');
246 cache
.warnings
= compileMessages
;
252 ideClient
.stderr
.once('data', function (data
) {
253 return reject(data
.toString());
256 ideClient
.stdin
.write(JSON
.stringify(body
));
257 ideClient
.stdin
.write('\n');
264 file: psModule
.srcPath
269 function formatIdeResult(result
, options
, index
, length
) {
270 var srcPath
= path
.relative(options
.context
, result
.filename
);
271 var pos
= result
.position
;
272 var fileAndPos
= srcPath
+ ':' + pos
.startLine
+ ':' + pos
.startColumn
;
273 var numAndErr
= '[' + (index
+ 1) + '/' + length
+ ' ' + result
.errorCode
+ ']';
274 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
;
276 return fs
.readFileAsync(result
.filename
, 'utf8').then(function (source
) {
277 var lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
);
278 var endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
;
279 var up
= options
.pscIdeColors
? colors
.red('^') : '^';
280 var down
= options
.pscIdeColors
? colors
.red('v') : 'v';
281 var trimmed
= lines
.slice(0);
284 lines
.splice(lines
.length
- 1, 1);
285 pos
.endLine
= pos
.endLine
- 1;
286 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1;
289 // strip newlines at the end
291 trimmed
= lines
.reverse().reduce(function (trimmed
, line
, i
) {
292 if (i
=== 0 && line
=== '') trimmed
.trimming
= true;
293 if (!trimmed
.trimming
) trimmed
.push(line
);
294 if (trimmed
.trimming
&& line
!== '') {
295 trimmed
.trimming
= false;
300 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
);
301 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1;
304 var spaces
= ' '.repeat(String(pos
.endLine
).length
);
305 var snippet
= trimmed
.map(function (line
, i
) {
306 return ' ' + (pos
.startLine
+ i
) + ' ' + line
;
309 if (trimmed
.length
=== 1) {
310 snippet
+= '\n ' + spaces
+ ' ' + ' '.repeat(pos
.startColumn
- 1) + up
.repeat(pos
.endColumn
- pos
.startColumn
+ 1);
312 snippet
= ' ' + spaces
+ ' ' + ' '.repeat(pos
.startColumn
- 1) + down
+ '\n' + snippet
;
313 snippet
+= '\n ' + spaces
+ ' ' + ' '.repeat(pos
.endColumn
- 1) + up
;
316 return Promise
.resolve('\n' + numAndErr
+ ' ' + fileAndPos
+ '\n\n' + snippet
+ '\n\n' + result
.message
);
320 function bundle(options
, cache
) {
321 if (cache
.bundle
) return Promise
.resolve(cache
.bundle
);
324 var stderr
= cache
.bundle
= [];
326 var args
= dargs(Object
.assign({
327 _: [path
.join(options
.output
, '*', '*.js')],
328 output: options
.bundleOutput
,
329 namespace: options
.bundleNamespace
330 }, options
.pscBundleArgs
));
332 cache
.bundleModules
.forEach(function (name
) {
333 return args
.push('--module', name
);
336 debug('spawning bundler %s %o', options
.pscBundle
, args
.join(' '));
338 return new Promise(function (resolve
, reject
) {
339 console
.log('Bundling PureScript...');
341 var compilation
= spawn(options
.pscBundle
, args
);
343 compilation
.stdout
.on('data', function (data
) {
344 return stdout
.push(data
.toString());
346 compilation
.stderr
.on('data', function (data
) {
347 return stderr
.push(data
.toString());
349 compilation
.on('close', function (code
) {
351 cache
.errors
.concat(stderr
);
354 cache
.bundle
= stderr
;
355 resolve(fs
.appendFileAsync('output/bundle.js', 'module.exports = ' + options
.bundleNamespace
));
360 // map of PS module names to their source path
361 function psModuleMap(globs
, cache
) {
362 if (cache
.psModuleMap
) return Promise
.resolve(cache
.psModuleMap
);
364 return globby(globs
).then(function (paths
) {
365 return Promise
.props(paths
.reduce(function (map
, file
) {
366 map
[file
] = fs
.readFileAsync(file
, 'utf8');
368 }, {})).then(function (srcMap
) {
369 cache
.psModuleMap
= Object
.keys(srcMap
).reduce(function (map
, file
) {
370 var source
= srcMap
[file
];
371 var psModuleName
= match(psModuleRegex
, source
);
372 map
[psModuleName
] = path
.resolve(file
);
375 return cache
.psModuleMap
;
380 function connectIdeServer(psModule
) {
381 var options
= psModule
.options
;
382 var cache
= psModule
.cache
;
384 if (cache
.ideServer
) return Promise
.resolve(psModule
);
386 cache
.ideServer
= true;
388 var connect
= function connect() {
389 return new Promise(function (resolve
, reject
) {
390 var args
= dargs(options
.pscIdeArgs
);
392 debug('attempting to connect to psc-ide-server', args
);
394 var ideClient
= spawn('psc-ide-client', args
);
396 ideClient
.stderr
.on('data', function (data
) {
397 debug(data
.toString());
398 cache
.ideServer
= false;
401 ideClient
.stdout
.once('data', function (data
) {
402 debug(data
.toString());
403 if (data
.toString()[0] === '{') {
404 var res
= JSON
.parse(data
.toString());
405 if (res
.resultType
=== 'success') {
406 cache
.ideServer
= ideServer
;
409 cache
.ideServer
= ideServer
;
413 cache
.ideServer
= false;
417 ideClient
.stdin
.resume();
418 ideClient
.stdin
.write(JSON
.stringify({ command: 'load' }));
419 ideClient
.stdin
.write('\n');
423 var args
= dargs(Object
.assign({
424 outputDirectory: options
.output
425 }, options
.pscIdeArgs
));
427 debug('attempting to start psc-ide-server', args
);
429 var ideServer
= cache
.ideServer
= spawn('psc-ide-server', []);
430 ideServer
.stderr
.on('data', function (data
) {
431 debug(data
.toString());
434 return retryPromise(function (retry
, number
) {
435 return connect().catch(function (error
) {
436 if (!cache
.ideServer
&& number
=== 9) {
439 console
.log('failed to connect to or start psc-ide-server, ' + 'full compilation will occur on rebuild');
441 return Promise
.resolve(psModule
);
454 function match(regex
, str
) {
455 var matches
= str
.match(regex
);
456 return matches
&& matches
[1];
459 function dargs(obj
) {
460 return Object
.keys(obj
).reduce(function (args
, key
) {
461 var arg
= '--' + key
.replace(/[A-Z]/g, '-$&').toLowerCase();
464 if (key
=== '_') val
.forEach(function (v
) {
466 });else if (Array
.isArray(val
)) val
.forEach(function (v
) {
467 return args
.push(arg
, v
);
468 });else args
.push(arg
, obj
[key
]);