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