3 const debug_
= require('debug');
5 const debug
= debug_('purs-loader');
7 const debugVerbose
= debug_('purs-loader:verbose');
9 const loaderUtils
= require('loader-utils')
11 const Promise
= require('bluebird')
13 const path
= require('path')
15 const PsModuleMap
= require('./purs-module-map');
17 const compile
= require('./compile');
19 const bundle
= require('./bundle');
21 const ide
= require('./ide');
23 const toJavaScript
= require('./to-javascript');
25 const sourceMaps
= require('./source-maps');
27 const dargs
= require('./dargs');
29 const utils
= require('./utils');
31 const spawn
= require('cross-spawn').sync
33 const eol
= require('os').EOL
43 compilationStarted: false,
44 compilationFinished: false,
45 compilationFailed: false,
50 module
.exports
= function purescriptLoader(source
, map
) {
51 this.cacheable
&& this.cacheable();
53 const webpackContext
= (this.options
&& this.options
.context
) || this.rootContext
;
55 const callback
= this.async();
57 const loaderOptions
= loaderUtils
.getOptions(this) || {};
59 const srcOption
= (pscPackage
=> {
60 const srcPath
= path
.join('src', '**', '*.purs');
62 const bowerPath
= path
.join('bower_components', 'purescript-*', 'src', '**', '*.purs');
64 if (CACHE_VAR
.srcOption
.length
> 0) {
65 return CACHE_VAR
.srcOption
;
67 else if (pscPackage
) {
68 const pscPackageCommand
= 'psc-package';
70 const pscPackageArgs
= ['sources'];
72 const loaderSrc
= loaderOptions
.src
|| [
76 debug('psc-package %s %o', pscPackageCommand
, pscPackageArgs
);
78 const cmd
= spawn(pscPackageCommand
, pscPackageArgs
);
81 throw new Error(cmd
.error
);
83 else if (cmd
.status
!== 0) {
84 const error
= cmd
.stdout
.toString();
86 throw new Error(error
);
89 const result
= cmd
.stdout
.toString().split(eol
).filter(v
=> v
!= '').concat(loaderSrc
);
91 debug('psc-package result: %o', result
);
93 CACHE_VAR
.srcOption
= result
;
99 const result
= loaderOptions
.src
|| [
104 CACHE_VAR
.srcOption
= result
;
108 })(loaderOptions
.pscPackage
);
110 const options
= Object
.assign({
111 context: webpackContext
,
117 pscIdeClientArgs: {},
119 pscIdeServerArgs: {},
121 pscIdeColors: loaderOptions
.psc
=== 'psa',
123 bundleOutput: 'output/bundle.js',
124 bundleNamespace: 'PS',
135 if (!CACHE_VAR
.installed
) {
136 debugVerbose('installing purs-loader with options: %O', options
);
138 CACHE_VAR
.installed
= true;
140 // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode)
141 this._compiler
.plugin('invalid', () => {
142 debugVerbose('invalidating loader CACHE_VAR');
145 rebuild: options
.pscIde
,
148 ideServer: CACHE_VAR
.ideServer
,
149 psModuleMap: CACHE_VAR
.psModuleMap
,
152 compilationStarted: false,
153 compilationFinished: false,
154 compilationFailed: false,
155 installed: CACHE_VAR
.installed
,
160 // add psc warnings to webpack compilation warnings
161 this._compiler
.plugin('after-compile', (compilation
, callback
) => {
162 CACHE_VAR
.warnings
.forEach(warning
=> {
163 compilation
.warnings
.push(warning
);
166 CACHE_VAR
.errors
.forEach(error
=> {
167 compilation
.errors
.push(error
);
174 const psModuleName
= PsModuleMap
.matchModule(source
);
179 load: ({js
, map
}) => callback(null, js
, map
),
180 reject: error
=> callback(error
),
181 srcPath: this.resourcePath
,
182 remainingRequest: loaderUtils
.getRemainingRequest(this),
183 srcDir: path
.dirname(this.resourcePath
),
184 jsPath: path
.resolve(path
.join(options
.output
, psModuleName
, 'index.js')),
187 emitWarning: warning
=> {
188 if (options
.warnings
&& warning
.length
) {
189 CACHE_VAR
.warnings
.push(warning
);
192 emitError: pscMessage
=> {
193 if (pscMessage
.length
) {
196 const matchErrorsSeparator
= /\n(?=Error)/;
197 const errors
= pscMessage
.split(matchErrorsSeparator
);
198 for (const error
of errors
) {
199 const matchErrLocation
= /at (.+\.purs
):(\d
+):(\d
+) - (\d
+):(\d
+) \(line
\2, column
\3 - line
\4, column
\5\)/;
200 const [, filename
] = matchErrLocation
.exec(error
) || [];
201 if (!filename
) continue;
203 const baseModulePath
= path
.join(this.rootContext
, filename
);
204 this.addDependency(baseModulePath
);
206 const foreignModulesErrorCodes
= [
207 'ErrorParsingFFIModule',
208 'MissingFFIImplementations',
209 'UnusedFFIImplementations',
212 for (const code
of foreignModulesErrorCodes
) {
213 if (error
.includes(code
)) {
214 const resolved
= utils
.resolveForeignModule(baseModulePath
);
215 this.addDependency(resolved
);
219 const matchErrModuleName
= /in module ((?:\w
+\.)*\w
+)/;
220 const [, baseModuleName
] = matchErrModuleName
.exec(error
) || [];
221 if (!baseModuleName
) continue;
223 const matchMissingModuleName
= /Module ((?:\w
+\.)*\w
+) was not found
/;
224 const matchMissingImportFromModuleName
= /Cannot
import value
\w
+ from module ((?:\w
+\.)*\w
+)/;
225 for (const re
of [matchMissingModuleName
, matchMissingImportFromModuleName
]) {
226 const [, targetModuleName
] = re
.exec(error
) || [];
227 if (targetModuleName
) {
228 const resolved
= utils
.resolvePursModule({
231 rewriteRules: options
.rewriteRules
,
234 this.addDependency(resolved
);
239 name: baseModuleName
,
240 filename: baseModulePath
243 if (typeof this.describePscError
=== 'function') {
244 const { dependencies
= [], details
} = this.describePscError(error
, desc
);
246 for (const dep
of dependencies
) {
247 this.addDependency(dep
);
250 Object
.assign(desc
, details
);
256 CACHE_VAR
.errors
.push(new utils
.PscError(pscMessage
, modules
));
261 debug('loading %s', psModule
.name
);
263 if (options
.bundle
) {
264 CACHE_VAR
.bundleModules
.push(psModule
.name
);
267 if (CACHE_VAR
.rebuild
) {
268 const connect
= () => {
269 if (!CACHE_VAR
.ideServer
) {
270 CACHE_VAR
.ideServer
= true;
272 return ide
.connect(psModule
)
274 CACHE_VAR
.ideServer
= ideServer
;
277 .then(ide
.loadWithRetry
)
279 if (CACHE_VAR
.ideServer
.kill
) {
280 debug('ide failed to initially load modules, stopping the ide server process');
282 CACHE_VAR
.ideServer
.kill();
285 CACHE_VAR
.ideServer
= null;
287 return Promise
.reject(error
);
292 return Promise
.resolve(psModule
);
296 const rebuild
= () =>
297 ide
.rebuild(psModule
)
299 toJavaScript(psModule
)
300 .then(js
=> sourceMaps(psModule
, js
))
302 .catch(psModule
.reject
)
305 if (error
instanceof ide
.UnknownModuleError
) {
306 // Store the modules that trigger a recompile due to an
307 // unknown module error. We need to wait until compilation is
308 // done before loading these files.
310 CACHE_VAR
.deferred
.push(psModule
);
312 if (!CACHE_VAR
.compilationStarted
) {
313 CACHE_VAR
.compilationStarted
= true;
315 return compile(psModule
)
317 CACHE_VAR
.compilationFinished
= true;
320 Promise
.map(CACHE_VAR
.deferred
, psModule
=>
322 .then(() => toJavaScript(psModule
))
323 .then(js
=> sourceMaps(psModule
, js
))
328 CACHE_VAR
.compilationFailed
= true;
330 CACHE_VAR
.deferred
[0].reject(error
);
332 CACHE_VAR
.deferred
.slice(1).forEach(psModule
=> {
333 psModule
.reject(new Error('purs-loader failed'));
337 } else if (CACHE_VAR
.compilationFailed
) {
338 CACHE_VAR
.deferred
.pop().reject(new Error('purs-loader failed'));
340 // The compilation has started. We must wait until it is
341 // done in order to ensure the module map contains all of
342 // the unknown modules.
346 debug('ide rebuild failed due to an unhandled error: %o', error
);
348 psModule
.reject(error
);
353 connect().then(rebuild
);
355 else if (CACHE_VAR
.compilationFinished
) {
356 debugVerbose('compilation is already finished, loading module %s', psModule
.name
);
358 toJavaScript(psModule
)
359 .then(js
=> sourceMaps(psModule
, js
))
361 .catch(psModule
.reject
);
364 // The compilation has not finished yet. We need to wait for
365 // compilation to finish before the loaders run so that references
366 // to compiled output are valid. Push the modules into the CACHE_VAR to
367 // be loaded once the complation is complete.
369 CACHE_VAR
.deferred
.push(psModule
);
371 if (!CACHE_VAR
.compilationStarted
) {
372 CACHE_VAR
.compilationStarted
= true;
376 CACHE_VAR
.compilationFinished
= true;
379 if (options
.bundle
) {
380 return bundle(options
, CACHE_VAR
.bundleModules
);
384 Promise
.map(CACHE_VAR
.deferred
, psModule
=>
385 toJavaScript(psModule
)
386 .then(js
=> sourceMaps(psModule
, js
))
391 CACHE_VAR
.compilationFailed
= true;
393 CACHE_VAR
.deferred
[0].reject(error
);
395 CACHE_VAR
.deferred
.slice(1).forEach(psModule
=> {
396 psModule
.reject(new Error('purs-loader failed'));
400 } else if (CACHE_VAR
.compilationFailed
) {
401 CACHE_VAR
.deferred
.pop().reject(new Error('purs-loader failed'));
403 // The complation has started. Nothing to do but wait until it is
404 // done before loading all of the modules.