diff options
Diffstat (limited to 'src/index.js')
-rw-r--r-- | src/index.js | 226 |
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 | ||
3 | const debug = require('debug')('purs-loader') | 3 | const debug_ = require('debug'); |
4 | |||
5 | const debug = debug_('purs-loader'); | ||
6 | |||
7 | const debugVerbose = debug_('purs-loader:verbose'); | ||
4 | 8 | ||
5 | const loaderUtils = require('loader-utils') | 9 | const loaderUtils = require('loader-utils') |
6 | 10 | ||
@@ -25,33 +29,44 @@ const spawn = require('cross-spawn').sync | |||
25 | const eol = require('os').EOL | 29 | const eol = require('os').EOL |
26 | 30 | ||
27 | module.exports = function purescriptLoader(source, map) { | 31 | module.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 | } |