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