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