]> git.immae.eu Git - github/fretlink/purs-loader.git/blob - src/index.js
Update dependencies
[github/fretlink/purs-loader.git] / src / index.js
1 'use strict'
2
3 const debug_ = require('debug');
4
5 const debug = debug_('purs-loader');
6
7 const debugVerbose = debug_('purs-loader:verbose');
8
9 const loaderUtils = require('loader-utils')
10
11 const Promise = require('bluebird')
12
13 const path = require('path')
14
15 const PsModuleMap = require('./purs-module-map');
16
17 const compile = require('./compile');
18
19 const bundle = require('./bundle');
20
21 const ide = require('./ide');
22
23 const toJavaScript = require('./to-javascript');
24
25 const sourceMaps = require('./source-maps');
26
27 const spawn = require('cross-spawn').sync
28
29 const eol = require('os').EOL
30
31 var 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 spagoOutputPath: null
44 };
45
46 // include src files provided by psc-package or Spago
47 function 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
77 // 'spago output path' will return the output folder in a monorepo
78 function 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
107 module.exports = function purescriptLoader(source, map) {
108 this.cacheable && this.cacheable();
109
110 const webpackContext = (this.options && this.options.context) || this.rootContext;
111
112 const callback = this.async();
113
114 const loaderOptions = loaderUtils.getOptions(this) || {};
115
116 const srcOption = ((pscPackage, spago) => {
117 const srcPath = path.join('src', '**', '*.purs');
118
119 const bowerPath = path.join('bower_components', 'purescript-*', 'src', '**', '*.purs');
120
121 if (CACHE_VAR.srcOption.length > 0) {
122 return CACHE_VAR.srcOption;
123 }
124 else if (pscPackage) {
125 return requestDependencySources('psc-package', srcPath, loaderOptions)
126 }
127 else if (spago) {
128 return requestDependencySources('spago', srcPath, loaderOptions)
129 }
130 else {
131 const result = loaderOptions.src || [
132 bowerPath,
133 srcPath
134 ];
135
136 CACHE_VAR.srcOption = result;
137
138 return result;
139 }
140 })(loaderOptions.pscPackage, loaderOptions.spago);
141
142 const outputPath = loaderOptions.spago ? getSpagoSources() : 'output'
143
144 const options = Object.assign({
145 context: webpackContext,
146 psc: null,
147 pscArgs: {},
148 pscBundle: null,
149 pscBundleArgs: {},
150 pscIdeClient: null,
151 pscIdeClientArgs: {},
152 pscIdeServer: null,
153 pscIdeServerArgs: {},
154 pscIdeRebuildArgs: {},
155 pscIde: false,
156 pscIdeColors: loaderOptions.psc === 'psa',
157 pscPackage: false,
158 spago: false,
159 bundleOutput: 'output/bundle.js',
160 bundleNamespace: 'PS',
161 bundle: false,
162 warnings: true,
163 watch: false,
164 output: outputPath,
165 src: []
166 }, loaderOptions, {
167 src: srcOption
168 });
169
170 if (!CACHE_VAR.installed) {
171 debugVerbose('installing purs-loader with options: %O', options);
172
173 CACHE_VAR.installed = true;
174
175 const invalidCb = () => {
176 debugVerbose('invalidating loader CACHE_VAR');
177
178 CACHE_VAR = {
179 rebuild: options.pscIde,
180 deferred: [],
181 bundleModules: [],
182 ideServer: CACHE_VAR.ideServer,
183 psModuleMap: CACHE_VAR.psModuleMap,
184 warnings: [],
185 errors: [],
186 compilationStarted: false,
187 compilationFinished: false,
188 installed: CACHE_VAR.installed,
189 srcOption: []
190 };
191 }
192
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) => {
201 CACHE_VAR.warnings.forEach(warning => {
202 compilation.warnings.push(warning);
203 });
204
205 CACHE_VAR.errors.forEach(error => {
206 compilation.errors.push(error);
207 });
208
209 callback()
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 }
218 }
219
220 const psModuleName = PsModuleMap.matchModule(source);
221
222 const psModule = {
223 name: psModuleName,
224 source: source,
225 load: ({js, map}) => callback(null, js, map),
226 reject: error => callback(error),
227 srcPath: this.resourcePath,
228 remainingRequest: loaderUtils.getRemainingRequest(this),
229 srcDir: path.dirname(this.resourcePath),
230 jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')),
231 options: options,
232 cache: CACHE_VAR,
233 emitWarning: warning => {
234 if (options.warnings && warning.length) {
235 CACHE_VAR.warnings.push(warning);
236 }
237 },
238 emitError: error => {
239 if (error.length) {
240 CACHE_VAR.errors.push(error);
241 }
242 }
243 }
244
245 debug('loading %s', psModule.name);
246
247 if (options.bundle) {
248 CACHE_VAR.bundleModules.push(psModule.name);
249 }
250
251 if (CACHE_VAR.rebuild) {
252 const connect = () => {
253 if (!CACHE_VAR.ideServer) {
254 CACHE_VAR.ideServer = true;
255
256 return ide.connect(psModule)
257 .then(ideServer => {
258 CACHE_VAR.ideServer = ideServer;
259 return psModule;
260 })
261 .then(ide.loadWithRetry)
262 .catch(error => {
263 if (CACHE_VAR.ideServer.kill) {
264 debug('ide failed to initially load modules, stopping the ide server process');
265
266 CACHE_VAR.ideServer.kill();
267 }
268
269 CACHE_VAR.ideServer = null;
270
271 return Promise.reject(error);
272 })
273 ;
274 }
275 else {
276 return Promise.resolve(psModule);
277 }
278 };
279
280 const rebuild = () =>
281 ide.rebuild(psModule)
282 .then(() =>
283 toJavaScript(psModule)
284 .then(js => sourceMaps(psModule, js))
285 .then(psModule.load)
286 .catch(psModule.reject)
287 )
288 .catch(error => {
289 if (error instanceof ide.UnknownModuleError) {
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
294 CACHE_VAR.deferred.push(psModule);
295
296 if (!CACHE_VAR.compilationStarted) {
297 CACHE_VAR.compilationStarted = true;
298
299 return compile(psModule)
300 .then(() => {
301 CACHE_VAR.compilationFinished = true;
302 })
303 .then(() =>
304 Promise.map(CACHE_VAR.deferred, psModule =>
305 ide.load(psModule)
306 .then(() => toJavaScript(psModule))
307 .then(js => sourceMaps(psModule, js))
308 .then(psModule.load)
309 )
310 )
311 .catch(error => {
312 CACHE_VAR.deferred[0].reject(error);
313
314 CACHE_VAR.deferred.slice(1).forEach(psModule => {
315 psModule.reject(new Error('purs-loader failed'));
316 })
317 })
318 ;
319 }
320 else {
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.
324 }
325 }
326 else {
327 debug('ide rebuild failed due to an unhandled error: %o', error);
328
329 psModule.reject(error);
330 }
331 })
332 ;
333
334 connect().then(rebuild);
335 }
336 else if (CACHE_VAR.compilationFinished) {
337 debugVerbose('compilation is already finished, loading module %s', psModule.name);
338
339 toJavaScript(psModule)
340 .then(js => sourceMaps(psModule, js))
341 .then(psModule.load)
342 .catch(psModule.reject);
343 }
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
347 // to compiled output are valid. Push the modules into the CACHE_VAR to
348 // be loaded once the complation is complete.
349
350 CACHE_VAR.deferred.push(psModule);
351
352 if (!CACHE_VAR.compilationStarted) {
353 CACHE_VAR.compilationStarted = true;
354
355 compile(psModule)
356 .then(() => {
357 CACHE_VAR.compilationFinished = true;
358 })
359 .then(() => {
360 if (options.bundle) {
361 return bundle(options, CACHE_VAR.bundleModules);
362 }
363 })
364 .then(() =>
365 Promise.map(CACHE_VAR.deferred, psModule =>
366 toJavaScript(psModule)
367 .then(js => sourceMaps(psModule, js))
368 .then(psModule.load)
369 )
370 )
371 .catch(error => {
372 CACHE_VAR.deferred[0].reject(error);
373
374 CACHE_VAR.deferred.slice(1).forEach(psModule => {
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 }
384 }
385 }