]> git.immae.eu Git - github/fretlink/purs-loader.git/blob - src/index.js
7cf942c912b1493b3e1c05407bcb6e01065c87d1
[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 callback = this.async();
35
36 const webpackConfig = this.options;
37
38 const loaderOptions = loaderUtils.getOptions(this) || {};
39
40 const srcOption = (pscPackage => {
41 if (pscPackage) {
42 const pscPackageCommand = 'psc-package';
43
44 const pscPackageArgs = ['sources'];
45
46 debug('psc-package %s %o', pscPackageCommand, pscPackageArgs);
47
48 return spawn(pscPackageCommand, pscPackageArgs).stdout.toString().split(eol).filter(v => v != '').concat(
49 loaderOptions.src || [
50 path.join('src', '**', '*.purs'),
51 ]
52 )
53 }
54 else {
55 return loaderOptions.src || [
56 path.join('bower_components', 'purescript-*', 'src', '**', '*.purs'),
57 path.join('src', '**', '*.purs'),
58 ];
59 }
60 })(loaderOptions.pscPackage);
61
62 const options = Object.assign({
63 context: webpackConfig.context,
64 psc: null,
65 pscArgs: {},
66 pscBundle: null,
67 pscBundleArgs: {},
68 pscIde: false,
69 pscIdeColors: loaderOptions.psc === 'psa',
70 pscIdeArgs: {},
71 pscPackage: false,
72 bundleOutput: 'output/bundle.js',
73 bundleNamespace: 'PS',
74 bundle: false,
75 warnings: true,
76 watch: false,
77 output: 'output',
78 src: []
79 }, loaderOptions, {
80 src: srcOption
81 });
82
83 var cache = webpackConfig.purescriptLoaderCache = webpackConfig.purescriptLoaderCache || {
84 rebuild: false,
85 deferred: [],
86 bundleModules: [],
87 warnings: [],
88 errors: []
89 };
90
91 if (!webpackConfig.purescriptLoaderInstalled) {
92 debugVerbose('installing purs-loader with options: %O', options);
93
94 webpackConfig.purescriptLoaderInstalled = true
95
96 // invalidate loader cache when bundle is marked as invalid (in watch mode)
97 this._compiler.plugin('invalid', () => {
98 debugVerbose('invalidating loader cache');
99
100 cache = webpackConfig.purescriptLoaderCache = {
101 rebuild: options.pscIde,
102 deferred: [],
103 bundleModules: [],
104 ideServer: cache.ideServer,
105 psModuleMap: cache.psModuleMap,
106 warnings: [],
107 errors: []
108 };
109 });
110
111 // add psc warnings to webpack compilation warnings
112 this._compiler.plugin('after-compile', (compilation, callback) => {
113 cache.warnings.forEach(warning => {
114 compilation.warnings.push(warning);
115 });
116
117 cache.errors.forEach(error => {
118 compilation.errors.push(error);
119 });
120
121 callback()
122 });
123 }
124
125 const psModuleName = PsModuleMap.matchModule(source);
126
127 const psModule = {
128 name: psModuleName,
129 load: js => callback(null, js),
130 reject: error => callback(error),
131 srcPath: this.resourcePath,
132 srcDir: path.dirname(this.resourcePath),
133 jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')),
134 options: options,
135 cache: cache,
136 emitWarning: warning => {
137 if (options.warnings && warning.length) {
138 cache.warnings.push(warning);
139 }
140 },
141 emitError: error => {
142 if (error.length) {
143 cache.errors.push(error);
144 }
145 }
146 }
147
148 debug('loading %s', psModule.name);
149
150 if (options.bundle) {
151 cache.bundleModules.push(psModule.name);
152 }
153
154 if (cache.rebuild) {
155 const connect = () => {
156 if (!cache.ideServer) {
157 cache.ideServer = true;
158
159 return ide.connect(psModule)
160 .then(ideServer => {
161 cache.ideServer = ideServer;
162 return psModule;
163 })
164 .then(ide.loadWithRetry)
165 .catch(error => {
166 if (cache.ideServer.kill) {
167 debug('ide failed to initially load modules, stopping the ide server process');
168
169 cache.ideServer.kill();
170 }
171
172 cache.ideServer = null;
173
174 return Promise.reject(error);
175 })
176 ;
177 }
178 else {
179 return Promise.resolve(psModule);
180 }
181 };
182
183 const rebuild = () =>
184 ide.rebuild(psModule).catch(error => {
185 if (error instanceof ide.UnknownModuleError) {
186 if (!cache.compilationStarted) {
187 cache.compilationStarted = true;
188
189 return compile(psModule)
190 .then(() => {
191 cache.compilationFinished = true;
192 })
193 .then(() =>
194 PsModuleMap.makeMap(options.src).then(map => {
195 debug('rebuilt module map after unknown module forced a recompilation');
196
197 cache.psModuleMap = map;
198 })
199 )
200 .then(() => ide.load(psModule))
201 .then(() => psModule)
202 ;
203 }
204 else {
205 return Promise.resolve(psModule);
206 }
207 }
208 else {
209 debug('ide rebuild failed due to an unhandled error: %o', error);
210
211 return Promise.reject(error);
212 }
213 })
214 ;
215
216 connect()
217 .then(rebuild)
218 .then(toJavaScript)
219 .then(psModule.load)
220 .catch(psModule.reject)
221 ;
222 }
223 else if (cache.compilationFinished) {
224 debugVerbose('compilation is already finished, loading module %s', psModule.name);
225
226 toJavaScript(psModule)
227 .then(psModule.load)
228 .catch(psModule.reject);
229 }
230 else {
231 // The compilation has not finished yet. We need to wait for
232 // compilation to finish before the loaders run so that references
233 // to compiled output are valid. Push the modules into the cache to
234 // be loaded once the complation is complete.
235
236 cache.deferred.push(psModule);
237
238 if (!cache.compilationStarted) {
239 cache.compilationStarted = true;
240
241 compile(psModule)
242 .then(() => {
243 cache.compilationFinished = true;
244 })
245 .then(() => {
246 if (options.bundle) {
247 return bundle(options, cache.bundleModules);
248 }
249 })
250 .then(() =>
251 PsModuleMap.makeMap(options.src).then(map => {
252 debug('rebuilt module map after compilation');
253
254 cache.psModuleMap = map;
255 })
256 )
257 .then(() =>
258 Promise.map(cache.deferred, psModule =>
259 toJavaScript(psModule).then(psModule.load)
260 )
261 )
262 .catch(error => {
263 cache.deferred[0].reject(error);
264
265 cache.deferred.slice(1).forEach(psModule => {
266 psModule.reject(new Error('purs-loader failed'));
267 })
268 })
269 ;
270 }
271 else {
272 // The complation has started. Nothing to do but wait until it is
273 // done before loading all of the modules.
274 }
275 }
276 }