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