]> git.immae.eu Git - github/fretlink/purs-loader.git/blame - src/index.js
Use 'spago path output' to choose default output path (#132)
[github/fretlink/purs-loader.git] / src / index.js
CommitLineData
7de41f10
AM
1'use strict'
2
7f0547d4 3const debug_ = require('debug');
4
5const debug = debug_('purs-loader');
6
7const debugVerbose = debug_('purs-loader:verbose');
1c12889c 8
7de41f10 9const loaderUtils = require('loader-utils')
1c12889c 10
7de41f10 11const Promise = require('bluebird')
1c12889c 12
7de41f10 13const path = require('path')
1c12889c 14
15const PsModuleMap = require('./purs-module-map');
16
17const compile = require('./compile');
18
19const bundle = require('./bundle');
20
21const ide = require('./ide');
22
8e21ab0a 23const toJavaScript = require('./to-javascript');
1c12889c 24
466c0068 25const sourceMaps = require('./source-maps');
26
86e2b3d4 27const spawn = require('cross-spawn').sync
1c12889c 28
86e2b3d4 29const eol = require('os').EOL
7de41f10 30
0f9fe1ed 31var CACHE_VAR = {
32 rebuild: false,
33 deferred: [],
34 bundleModules: [],
35 ideServer: null,
36 psModuleMap: null,
37 warnings: [],
38 errors: [],
39 compilationStarted: false,
40 compilationFinished: false,
41 installed: false,
1029c032
DH
42 srcOption: [],
43 spagoOutputPath: null
0f9fe1ed 44};
45
bef7f4e2
DH
46// include src files provided by psc-package or Spago
47function requestDependencySources(packagerCommand, srcPath, loaderOptions) {
48 const packagerArgs = ['sources'];
49
50 const loaderSrc = loaderOptions.src || [
51 srcPath
52 ];
53
54 debug('%s %o', packagerCommand, packagerArgs);
55
56 const cmd = spawn(packagerCommand, packagerArgs);
57
58 if (cmd.error) {
59 throw new Error(cmd.error);
60 }
61 else if (cmd.status !== 0) {
62 const error = cmd.stdout.toString();
63
64 throw new Error(error);
65 }
66 else {
67 const result = cmd.stdout.toString().split(eol).filter(v => v != '').concat(loaderSrc);
68
69 debug('%s result: %o', packagerCommand, result);
70
71 CACHE_VAR.srcOption = result;
72
73 return result;
74 }
75}
76
1029c032
DH
77// 'spago output path' will return the output folder in a monorepo
78function getSpagoSources() {
79 const cachedVal = CACHE_VAR.spagoOutputPath;
80 if (cachedVal) {
81 return cachedVal
82 }
83 const command = "spago"
84 const args = ["path", "output"]
85
86 const cmd = spawn(command, args);
87
88 if (cmd.error) {
89 throw new Error(cmd.error);
90 }
91 else if (cmd.status !== 0) {
92 const error = cmd.stdout.toString();
93
94 throw new Error(error);
95 }
96 else {
97 const result = cmd.stdout.toString().split(eol)[0]
98
99 debug('"spago path output" result: %o', result);
100
101 CACHE_VAR.spagoOutputPath = result;
102
103 return result;
104 }
105}
106
7de41f10 107module.exports = function purescriptLoader(source, map) {
7f0547d4 108 this.cacheable && this.cacheable();
109
0f9fe1ed 110 const webpackContext = (this.options && this.options.context) || this.rootContext;
e1719658 111
112 const callback = this.async();
113
7f0547d4 114 const loaderOptions = loaderUtils.getOptions(this) || {};
7de41f10 115
bef7f4e2 116 const srcOption = ((pscPackage, spago) => {
e1719658 117 const srcPath = path.join('src', '**', '*.purs');
118
119 const bowerPath = path.join('bower_components', 'purescript-*', 'src', '**', '*.purs');
120
0f9fe1ed 121 if (CACHE_VAR.srcOption.length > 0) {
122 return CACHE_VAR.srcOption;
e1719658 123 }
124 else if (pscPackage) {
bef7f4e2 125 return requestDependencySources('psc-package', srcPath, loaderOptions)
86e2b3d4 126 }
bef7f4e2
DH
127 else if (spago) {
128 return requestDependencySources('spago', srcPath, loaderOptions)
129 }
86e2b3d4 130 else {
e1719658 131 const result = loaderOptions.src || [
132 bowerPath,
133 srcPath
7f0547d4 134 ];
e1719658 135
0f9fe1ed 136 CACHE_VAR.srcOption = result;
e1719658 137
138 return result;
86e2b3d4 139 }
bef7f4e2 140 })(loaderOptions.pscPackage, loaderOptions.spago);
1029c032
DH
141
142 const outputPath = loaderOptions.spago ? getSpagoSources() : 'output'
86e2b3d4 143
7f0547d4 144 const options = Object.assign({
0f9fe1ed 145 context: webpackContext,
1c12889c 146 psc: null,
7de41f10 147 pscArgs: {},
1c12889c 148 pscBundle: null,
7de41f10 149 pscBundleArgs: {},
71a96808 150 pscIdeClient: null,
151 pscIdeClientArgs: {},
152 pscIdeServer: null,
153 pscIdeServerArgs: {},
cd124999 154 pscIdeRebuildArgs: {},
5163b5a2 155 pscIde: false,
7f0547d4 156 pscIdeColors: loaderOptions.psc === 'psa',
86e2b3d4 157 pscPackage: false,
bef7f4e2 158 spago: false,
7de41f10
AM
159 bundleOutput: 'output/bundle.js',
160 bundleNamespace: 'PS',
161 bundle: false,
162 warnings: true,
df8798fa 163 watch: false,
1029c032 164 output: outputPath,
7f0547d4 165 src: []
166 }, loaderOptions, {
167 src: srcOption
168 });
7de41f10 169
0f9fe1ed 170 if (!CACHE_VAR.installed) {
7f0547d4 171 debugVerbose('installing purs-loader with options: %O', options);
86e2b3d4 172
0f9fe1ed 173 CACHE_VAR.installed = true;
7de41f10 174
d841685e 175 const invalidCb = () => {
0f9fe1ed 176 debugVerbose('invalidating loader CACHE_VAR');
531c751f 177
0f9fe1ed 178 CACHE_VAR = {
5163b5a2 179 rebuild: options.pscIde,
7de41f10 180 deferred: [],
531c751f 181 bundleModules: [],
0f9fe1ed 182 ideServer: CACHE_VAR.ideServer,
183 psModuleMap: CACHE_VAR.psModuleMap,
b683b0b1 184 warnings: [],
e1719658 185 errors: [],
78e2b0d9 186 compilationStarted: false,
187 compilationFinished: false,
0f9fe1ed 188 installed: CACHE_VAR.installed,
6ccb09a5 189 srcOption: []
7f0547d4 190 };
d841685e 191 }
b683b0b1 192
d841685e
RS
193 // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode)
194 if(this._compiler.hooks){
195 this._compiler.hooks.invalid.tap('purs-loader', invalidCb);
196 } else {
197 this._compiler.plugin('invalid', invalidCb);
198 }
199
200 const afterCompileCb = (compilation, callback) => {
0f9fe1ed 201 CACHE_VAR.warnings.forEach(warning => {
b683b0b1 202 compilation.warnings.push(warning);
203 });
204
0f9fe1ed 205 CACHE_VAR.errors.forEach(error => {
b683b0b1 206 compilation.errors.push(error);
207 });
208
209 callback()
d841685e
RS
210 }
211
212 // add psc warnings to webpack compilation warnings
213 if(this._compiler.hooks) {
214 this._compiler.hooks.afterCompile.tapAsync('purs-loader', afterCompileCb);
215 } else {
216 this._compiler.plugin('after-compile', afterCompileCb);
217 }
7de41f10
AM
218 }
219
7f0547d4 220 const psModuleName = PsModuleMap.matchModule(source);
221
7de41f10
AM
222 const psModule = {
223 name: psModuleName,
466c0068 224 source: source,
225 load: ({js, map}) => callback(null, js, map),
7de41f10
AM
226 reject: error => callback(error),
227 srcPath: this.resourcePath,
466c0068 228 remainingRequest: loaderUtils.getRemainingRequest(this),
7de41f10
AM
229 srcDir: path.dirname(this.resourcePath),
230 jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')),
231 options: options,
0f9fe1ed 232 cache: CACHE_VAR,
b683b0b1 233 emitWarning: warning => {
234 if (options.warnings && warning.length) {
0f9fe1ed 235 CACHE_VAR.warnings.push(warning);
b683b0b1 236 }
237 },
238 emitError: error => {
239 if (error.length) {
0f9fe1ed 240 CACHE_VAR.errors.push(error);
b683b0b1 241 }
242 }
7de41f10
AM
243 }
244
7f0547d4 245 debug('loading %s', psModule.name);
67888496 246
7de41f10 247 if (options.bundle) {
0f9fe1ed 248 CACHE_VAR.bundleModules.push(psModule.name);
7de41f10
AM
249 }
250
0f9fe1ed 251 if (CACHE_VAR.rebuild) {
7f0547d4 252 const connect = () => {
0f9fe1ed 253 if (!CACHE_VAR.ideServer) {
254 CACHE_VAR.ideServer = true;
7f0547d4 255
256 return ide.connect(psModule)
257 .then(ideServer => {
0f9fe1ed 258 CACHE_VAR.ideServer = ideServer;
7f0547d4 259 return psModule;
260 })
261 .then(ide.loadWithRetry)
262 .catch(error => {
0f9fe1ed 263 if (CACHE_VAR.ideServer.kill) {
7f0547d4 264 debug('ide failed to initially load modules, stopping the ide server process');
265
0f9fe1ed 266 CACHE_VAR.ideServer.kill();
7f0547d4 267 }
268
0f9fe1ed 269 CACHE_VAR.ideServer = null;
7f0547d4 270
271 return Promise.reject(error);
272 })
273 ;
274 }
275 else {
276 return Promise.resolve(psModule);
277 }
278 };
279
280 const rebuild = () =>
78e2b0d9 281 ide.rebuild(psModule)
282 .then(() =>
283 toJavaScript(psModule)
466c0068 284 .then(js => sourceMaps(psModule, js))
78e2b0d9 285 .then(psModule.load)
286 .catch(psModule.reject)
287 )
288 .catch(error => {
7f0547d4 289 if (error instanceof ide.UnknownModuleError) {
78e2b0d9 290 // Store the modules that trigger a recompile due to an
291 // unknown module error. We need to wait until compilation is
292 // done before loading these files.
293
0f9fe1ed 294 CACHE_VAR.deferred.push(psModule);
78e2b0d9 295
0f9fe1ed 296 if (!CACHE_VAR.compilationStarted) {
297 CACHE_VAR.compilationStarted = true;
7f0547d4 298
299 return compile(psModule)
300 .then(() => {
0f9fe1ed 301 CACHE_VAR.compilationFinished = true;
7f0547d4 302 })
78e2b0d9 303 .then(() =>
0f9fe1ed 304 Promise.map(CACHE_VAR.deferred, psModule =>
78e2b0d9 305 ide.load(psModule)
306 .then(() => toJavaScript(psModule))
466c0068 307 .then(js => sourceMaps(psModule, js))
78e2b0d9 308 .then(psModule.load)
309 )
310 )
311 .catch(error => {
0f9fe1ed 312 CACHE_VAR.deferred[0].reject(error);
78e2b0d9 313
0f9fe1ed 314 CACHE_VAR.deferred.slice(1).forEach(psModule => {
78e2b0d9 315 psModule.reject(new Error('purs-loader failed'));
316 })
317 })
7f0547d4 318 ;
319 }
320 else {
78e2b0d9 321 // The compilation has started. We must wait until it is
322 // done in order to ensure the module map contains all of
323 // the unknown modules.
7f0547d4 324 }
325 }
326 else {
327 debug('ide rebuild failed due to an unhandled error: %o', error);
328
78e2b0d9 329 psModule.reject(error);
7f0547d4 330 }
331 })
332 ;
333
78e2b0d9 334 connect().then(rebuild);
7de41f10 335 }
0f9fe1ed 336 else if (CACHE_VAR.compilationFinished) {
7f0547d4 337 debugVerbose('compilation is already finished, loading module %s', psModule.name);
7de41f10 338
7f0547d4 339 toJavaScript(psModule)
466c0068 340 .then(js => sourceMaps(psModule, js))
7f0547d4 341 .then(psModule.load)
342 .catch(psModule.reject);
7de41f10 343 }
7f0547d4 344 else {
345 // The compilation has not finished yet. We need to wait for
346 // compilation to finish before the loaders run so that references
0f9fe1ed 347 // to compiled output are valid. Push the modules into the CACHE_VAR to
7f0547d4 348 // be loaded once the complation is complete.
349
0f9fe1ed 350 CACHE_VAR.deferred.push(psModule);
7f0547d4 351
0f9fe1ed 352 if (!CACHE_VAR.compilationStarted) {
353 CACHE_VAR.compilationStarted = true;
7f0547d4 354
355 compile(psModule)
356 .then(() => {
0f9fe1ed 357 CACHE_VAR.compilationFinished = true;
7f0547d4 358 })
359 .then(() => {
360 if (options.bundle) {
0f9fe1ed 361 return bundle(options, CACHE_VAR.bundleModules);
7f0547d4 362 }
363 })
7f0547d4 364 .then(() =>
0f9fe1ed 365 Promise.map(CACHE_VAR.deferred, psModule =>
78e2b0d9 366 toJavaScript(psModule)
466c0068 367 .then(js => sourceMaps(psModule, js))
78e2b0d9 368 .then(psModule.load)
7f0547d4 369 )
370 )
371 .catch(error => {
0f9fe1ed 372 CACHE_VAR.deferred[0].reject(error);
7f0547d4 373
0f9fe1ed 374 CACHE_VAR.deferred.slice(1).forEach(psModule => {
7f0547d4 375 psModule.reject(new Error('purs-loader failed'));
376 })
377 })
378 ;
379 }
380 else {
381 // The complation has started. Nothing to do but wait until it is
382 // done before loading all of the modules.
383 }
7de41f10
AM
384 }
385}