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