aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.js')
-rw-r--r--src/index.js226
1 files changed, 158 insertions, 68 deletions
diff --git a/src/index.js b/src/index.js
index 799f8f9..7cf942c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,10 @@
1'use strict' 1'use strict'
2 2
3const debug = require('debug')('purs-loader') 3const debug_ = require('debug');
4
5const debug = debug_('purs-loader');
6
7const debugVerbose = debug_('purs-loader:verbose');
4 8
5const loaderUtils = require('loader-utils') 9const loaderUtils = require('loader-utils')
6 10
@@ -25,33 +29,44 @@ const spawn = require('cross-spawn').sync
25const eol = require('os').EOL 29const eol = require('os').EOL
26 30
27module.exports = function purescriptLoader(source, map) { 31module.exports = function purescriptLoader(source, map) {
28 const callback = this.async() 32 this.cacheable && this.cacheable();
29 const config = this.options 33
30 const query = loaderUtils.getOptions(this) || {} 34 const callback = this.async();
31 const webpackOptions = this.options.purescriptLoader || {} 35
36 const webpackConfig = this.options;
37
38 const loaderOptions = loaderUtils.getOptions(this) || {};
32 39
33 const depsPaths = (pscPackage => { 40 const srcOption = (pscPackage => {
34 if (pscPackage) { 41 if (pscPackage) {
35 debug('calling psc-package...') 42 const pscPackageCommand = 'psc-package';
36 43
37 return spawn('psc-package', ['sources']).stdout.toString().split(eol).filter(v => v != '') 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 )
38 } 53 }
39 else { 54 else {
40 return [ path.join('bower_components', 'purescript-*', 'src', '**', '*.purs') ] 55 return loaderOptions.src || [
56 path.join('bower_components', 'purescript-*', 'src', '**', '*.purs'),
57 path.join('src', '**', '*.purs'),
58 ];
41 } 59 }
42 }) 60 })(loaderOptions.pscPackage);
43
44 let options = Object.assign(webpackOptions, query)
45 61
46 const defaultDeps = depsPaths(options.pscPackage) 62 const options = Object.assign({
47 const defaultOptions = { 63 context: webpackConfig.context,
48 context: config.context,
49 psc: null, 64 psc: null,
50 pscArgs: {}, 65 pscArgs: {},
51 pscBundle: null, 66 pscBundle: null,
52 pscBundleArgs: {}, 67 pscBundleArgs: {},
53 pscIde: false, 68 pscIde: false,
54 pscIdeColors: options.psc === 'psa', 69 pscIdeColors: loaderOptions.psc === 'psa',
55 pscIdeArgs: {}, 70 pscIdeArgs: {},
56 pscPackage: false, 71 pscPackage: false,
57 bundleOutput: 'output/bundle.js', 72 bundleOutput: 'output/bundle.js',
@@ -60,36 +75,29 @@ module.exports = function purescriptLoader(source, map) {
60 warnings: true, 75 warnings: true,
61 watch: false, 76 watch: false,
62 output: 'output', 77 output: 'output',
63 src: [ 78 src: []
64 path.join('src', '**', '*.purs'), 79 }, loaderOptions, {
65 ...defaultDeps 80 src: srcOption
66 ] 81 });
67 }
68
69 this.cacheable && this.cacheable()
70 82
71 let cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { 83 var cache = webpackConfig.purescriptLoaderCache = webpackConfig.purescriptLoaderCache || {
72 rebuild: false, 84 rebuild: false,
73 deferred: [], 85 deferred: [],
74 bundleModules: [], 86 bundleModules: [],
75 warnings: [], 87 warnings: [],
76 errors: [] 88 errors: []
77 } 89 };
78
79 if (options.pscPackage && options.src) {
80 options.src = options.src.concat(defaultDeps) // append psc-package-provided source paths with users'
81 }
82 90
83 options = Object.assign(defaultOptions, options) 91 if (!webpackConfig.purescriptLoaderInstalled) {
92 debugVerbose('installing purs-loader with options: %O', options);
84 93
85 if (!config.purescriptLoaderInstalled) { 94 webpackConfig.purescriptLoaderInstalled = true
86 config.purescriptLoaderInstalled = true
87 95
88 // invalidate loader cache when bundle is marked as invalid (in watch mode) 96 // invalidate loader cache when bundle is marked as invalid (in watch mode)
89 this._compiler.plugin('invalid', () => { 97 this._compiler.plugin('invalid', () => {
90 debug('invalidating loader cache'); 98 debugVerbose('invalidating loader cache');
91 99
92 cache = config.purescriptLoaderCache = { 100 cache = webpackConfig.purescriptLoaderCache = {
93 rebuild: options.pscIde, 101 rebuild: options.pscIde,
94 deferred: [], 102 deferred: [],
95 bundleModules: [], 103 bundleModules: [],
@@ -97,7 +105,7 @@ module.exports = function purescriptLoader(source, map) {
97 psModuleMap: cache.psModuleMap, 105 psModuleMap: cache.psModuleMap,
98 warnings: [], 106 warnings: [],
99 errors: [] 107 errors: []
100 } 108 };
101 }); 109 });
102 110
103 // add psc warnings to webpack compilation warnings 111 // add psc warnings to webpack compilation warnings
@@ -114,7 +122,8 @@ module.exports = function purescriptLoader(source, map) {
114 }); 122 });
115 } 123 }
116 124
117 const psModuleName = PsModuleMap.matchModule(source) 125 const psModuleName = PsModuleMap.matchModule(source);
126
118 const psModule = { 127 const psModule = {
119 name: psModuleName, 128 name: psModuleName,
120 load: js => callback(null, js), 129 load: js => callback(null, js),
@@ -136,51 +145,132 @@ module.exports = function purescriptLoader(source, map) {
136 } 145 }
137 } 146 }
138 147
139 debug('loader called', psModule.name) 148 debug('loading %s', psModule.name);
140 149
141 if (options.bundle) { 150 if (options.bundle) {
142 cache.bundleModules.push(psModule.name) 151 cache.bundleModules.push(psModule.name);
143 } 152 }
144 153
145 if (cache.rebuild) { 154 if (cache.rebuild) {
146 return ide.connect(psModule) 155 const connect = () => {
147 .then(ide.rebuild) 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)
148 .then(toJavaScript) 218 .then(toJavaScript)
149 .then(psModule.load) 219 .then(psModule.load)
150 .catch(psModule.reject) 220 .catch(psModule.reject)
221 ;
151 } 222 }
223 else if (cache.compilationFinished) {
224 debugVerbose('compilation is already finished, loading module %s', psModule.name);
152 225
153 if (cache.compilationFinished) { 226 toJavaScript(psModule)
154 return toJavaScript(psModule).then(psModule.load).catch(psModule.reject) 227 .then(psModule.load)
228 .catch(psModule.reject);
155 } 229 }
156 230 else {
157 // We need to wait for compilation to finish before the loaders run so that 231 // The compilation has not finished yet. We need to wait for
158 // references to compiled output are valid. 232 // compilation to finish before the loaders run so that references
159 cache.deferred.push(psModule) 233 // to compiled output are valid. Push the modules into the cache to
160 234 // be loaded once the complation is complete.
161 if (!cache.compilationStarted) { 235
162 cache.compilationStarted = true; 236 cache.deferred.push(psModule);
163 237
164 return compile(psModule) 238 if (!cache.compilationStarted) {
165 .then(() => { 239 cache.compilationStarted = true;
166 cache.compilationFinished = true; 240
167 241 compile(psModule)
168 const bundlePromise = options.bundle ? bundle(options, cache) : Promise.resolve(); 242 .then(() => {
169 243 cache.compilationFinished = true;
170 return bundlePromise.then(() => 244 })
245 .then(() => {
246 if (options.bundle) {
247 return bundle(options, cache.bundleModules);
248 }
249 })
250 .then(() =>
171 PsModuleMap.makeMap(options.src).then(map => { 251 PsModuleMap.makeMap(options.src).then(map => {
172 debug('rebuilt module map after compile'); 252 debug('rebuilt module map after compilation');
253
173 cache.psModuleMap = map; 254 cache.psModuleMap = map;
174 }) 255 })
175 ); 256 )
176 }) 257 .then(() =>
177 .then(() => Promise.map(cache.deferred, psModule => { 258 Promise.map(cache.deferred, psModule =>
178 if (typeof cache.ideServer === 'object') cache.ideServer.kill() 259 toJavaScript(psModule).then(psModule.load)
179 return toJavaScript(psModule).then(psModule.load) 260 )
180 })) 261 )
181 .catch(error => { 262 .catch(error => {
182 cache.deferred[0].reject(error) 263 cache.deferred[0].reject(error);
183 cache.deferred.slice(1).forEach(psModule => psModule.reject(new Error('purs-loader failed'))) 264
184 }) 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 }
185 } 275 }
186} 276}