]> git.immae.eu Git - github/fretlink/purs-loader.git/blame - src/index.js
Sourcemaps for ide rebuild (#118)
[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
466c0068 25const sourceMaps = require('./source-maps');
26
86e2b3d4 27const spawn = require('cross-spawn').sync
1c12889c 28
86e2b3d4 29const eol = require('os').EOL
7de41f10 30
0f9fe1ed 31var 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
7de41f10 45module.exports = function purescriptLoader(source, map) {
7f0547d4 46 this.cacheable && this.cacheable();
47
0f9fe1ed 48 const webpackContext = (this.options && this.options.context) || this.rootContext;
e1719658 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
0f9fe1ed 59 if (CACHE_VAR.srcOption.length > 0) {
60 return CACHE_VAR.srcOption;
e1719658 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
6ccb09a5 75 if (cmd.error) {
76 throw new Error(cmd.error);
77 }
78 else if (cmd.status !== 0) {
e1719658 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
0f9fe1ed 88 CACHE_VAR.srcOption = result;
e1719658 89
90 return result;
91 }
86e2b3d4
AD
92 }
93 else {
e1719658 94 const result = loaderOptions.src || [
95 bowerPath,
96 srcPath
7f0547d4 97 ];
e1719658 98
0f9fe1ed 99 CACHE_VAR.srcOption = result;
e1719658 100
101 return result;
86e2b3d4 102 }
7f0547d4 103 })(loaderOptions.pscPackage);
86e2b3d4 104
7f0547d4 105 const options = Object.assign({
0f9fe1ed 106 context: webpackContext,
1c12889c 107 psc: null,
7de41f10 108 pscArgs: {},
1c12889c 109 pscBundle: null,
7de41f10 110 pscBundleArgs: {},
71a96808 111 pscIdeClient: null,
112 pscIdeClientArgs: {},
113 pscIdeServer: null,
114 pscIdeServerArgs: {},
cd124999 115 pscIdeRebuildArgs: {},
5163b5a2 116 pscIde: false,
7f0547d4 117 pscIdeColors: loaderOptions.psc === 'psa',
86e2b3d4 118 pscPackage: false,
7de41f10
AM
119 bundleOutput: 'output/bundle.js',
120 bundleNamespace: 'PS',
121 bundle: false,
122 warnings: true,
df8798fa 123 watch: false,
7de41f10 124 output: 'output',
7f0547d4 125 src: []
126 }, loaderOptions, {
127 src: srcOption
128 });
7de41f10 129
0f9fe1ed 130 if (!CACHE_VAR.installed) {
7f0547d4 131 debugVerbose('installing purs-loader with options: %O', options);
86e2b3d4 132
0f9fe1ed 133 CACHE_VAR.installed = true;
7de41f10 134
0f9fe1ed 135 // invalidate loader CACHE_VAR when bundle is marked as invalid (in watch mode)
7de41f10 136 this._compiler.plugin('invalid', () => {
0f9fe1ed 137 debugVerbose('invalidating loader CACHE_VAR');
531c751f 138
0f9fe1ed 139 CACHE_VAR = {
5163b5a2 140 rebuild: options.pscIde,
7de41f10 141 deferred: [],
531c751f 142 bundleModules: [],
0f9fe1ed 143 ideServer: CACHE_VAR.ideServer,
144 psModuleMap: CACHE_VAR.psModuleMap,
b683b0b1 145 warnings: [],
e1719658 146 errors: [],
78e2b0d9 147 compilationStarted: false,
148 compilationFinished: false,
0f9fe1ed 149 installed: CACHE_VAR.installed,
6ccb09a5 150 srcOption: []
7f0547d4 151 };
b683b0b1 152 });
153
154 // add psc warnings to webpack compilation warnings
155 this._compiler.plugin('after-compile', (compilation, callback) => {
0f9fe1ed 156 CACHE_VAR.warnings.forEach(warning => {
b683b0b1 157 compilation.warnings.push(warning);
158 });
159
0f9fe1ed 160 CACHE_VAR.errors.forEach(error => {
b683b0b1 161 compilation.errors.push(error);
162 });
163
164 callback()
165 });
7de41f10
AM
166 }
167
7f0547d4 168 const psModuleName = PsModuleMap.matchModule(source);
169
7de41f10
AM
170 const psModule = {
171 name: psModuleName,
466c0068 172 source: source,
173 load: ({js, map}) => callback(null, js, map),
7de41f10
AM
174 reject: error => callback(error),
175 srcPath: this.resourcePath,
466c0068 176 remainingRequest: loaderUtils.getRemainingRequest(this),
7de41f10
AM
177 srcDir: path.dirname(this.resourcePath),
178 jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')),
179 options: options,
0f9fe1ed 180 cache: CACHE_VAR,
b683b0b1 181 emitWarning: warning => {
182 if (options.warnings && warning.length) {
0f9fe1ed 183 CACHE_VAR.warnings.push(warning);
b683b0b1 184 }
185 },
186 emitError: error => {
187 if (error.length) {
0f9fe1ed 188 CACHE_VAR.errors.push(error);
b683b0b1 189 }
190 }
7de41f10
AM
191 }
192
7f0547d4 193 debug('loading %s', psModule.name);
67888496 194
7de41f10 195 if (options.bundle) {
0f9fe1ed 196 CACHE_VAR.bundleModules.push(psModule.name);
7de41f10
AM
197 }
198
0f9fe1ed 199 if (CACHE_VAR.rebuild) {
7f0547d4 200 const connect = () => {
0f9fe1ed 201 if (!CACHE_VAR.ideServer) {
202 CACHE_VAR.ideServer = true;
7f0547d4 203
204 return ide.connect(psModule)
205 .then(ideServer => {
0f9fe1ed 206 CACHE_VAR.ideServer = ideServer;
7f0547d4 207 return psModule;
208 })
209 .then(ide.loadWithRetry)
210 .catch(error => {
0f9fe1ed 211 if (CACHE_VAR.ideServer.kill) {
7f0547d4 212 debug('ide failed to initially load modules, stopping the ide server process');
213
0f9fe1ed 214 CACHE_VAR.ideServer.kill();
7f0547d4 215 }
216
0f9fe1ed 217 CACHE_VAR.ideServer = null;
7f0547d4 218
219 return Promise.reject(error);
220 })
221 ;
222 }
223 else {
224 return Promise.resolve(psModule);
225 }
226 };
227
228 const rebuild = () =>
78e2b0d9 229 ide.rebuild(psModule)
230 .then(() =>
231 toJavaScript(psModule)
466c0068 232 .then(js => sourceMaps(psModule, js))
78e2b0d9 233 .then(psModule.load)
234 .catch(psModule.reject)
235 )
236 .catch(error => {
7f0547d4 237 if (error instanceof ide.UnknownModuleError) {
78e2b0d9 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
0f9fe1ed 242 CACHE_VAR.deferred.push(psModule);
78e2b0d9 243
0f9fe1ed 244 if (!CACHE_VAR.compilationStarted) {
245 CACHE_VAR.compilationStarted = true;
7f0547d4 246
247 return compile(psModule)
248 .then(() => {
0f9fe1ed 249 CACHE_VAR.compilationFinished = true;
7f0547d4 250 })
78e2b0d9 251 .then(() =>
0f9fe1ed 252 Promise.map(CACHE_VAR.deferred, psModule =>
78e2b0d9 253 ide.load(psModule)
254 .then(() => toJavaScript(psModule))
466c0068 255 .then(js => sourceMaps(psModule, js))
78e2b0d9 256 .then(psModule.load)
257 )
258 )
259 .catch(error => {
0f9fe1ed 260 CACHE_VAR.deferred[0].reject(error);
78e2b0d9 261
0f9fe1ed 262 CACHE_VAR.deferred.slice(1).forEach(psModule => {
78e2b0d9 263 psModule.reject(new Error('purs-loader failed'));
264 })
265 })
7f0547d4 266 ;
267 }
268 else {
78e2b0d9 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.
7f0547d4 272 }
273 }
274 else {
275 debug('ide rebuild failed due to an unhandled error: %o', error);
276
78e2b0d9 277 psModule.reject(error);
7f0547d4 278 }
279 })
280 ;
281
78e2b0d9 282 connect().then(rebuild);
7de41f10 283 }
0f9fe1ed 284 else if (CACHE_VAR.compilationFinished) {
7f0547d4 285 debugVerbose('compilation is already finished, loading module %s', psModule.name);
7de41f10 286
7f0547d4 287 toJavaScript(psModule)
466c0068 288 .then(js => sourceMaps(psModule, js))
7f0547d4 289 .then(psModule.load)
290 .catch(psModule.reject);
7de41f10 291 }
7f0547d4 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
0f9fe1ed 295 // to compiled output are valid. Push the modules into the CACHE_VAR to
7f0547d4 296 // be loaded once the complation is complete.
297
0f9fe1ed 298 CACHE_VAR.deferred.push(psModule);
7f0547d4 299
0f9fe1ed 300 if (!CACHE_VAR.compilationStarted) {
301 CACHE_VAR.compilationStarted = true;
7f0547d4 302
303 compile(psModule)
304 .then(() => {
0f9fe1ed 305 CACHE_VAR.compilationFinished = true;
7f0547d4 306 })
307 .then(() => {
308 if (options.bundle) {
0f9fe1ed 309 return bundle(options, CACHE_VAR.bundleModules);
7f0547d4 310 }
311 })
7f0547d4 312 .then(() =>
0f9fe1ed 313 Promise.map(CACHE_VAR.deferred, psModule =>
78e2b0d9 314 toJavaScript(psModule)
466c0068 315 .then(js => sourceMaps(psModule, js))
78e2b0d9 316 .then(psModule.load)
7f0547d4 317 )
318 )
319 .catch(error => {
0f9fe1ed 320 CACHE_VAR.deferred[0].reject(error);
7f0547d4 321
0f9fe1ed 322 CACHE_VAR.deferred.slice(1).forEach(psModule => {
7f0547d4 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 }
7de41f10
AM
332 }
333}