diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/bundle.js | 21 | ||||
-rw-r--r-- | src/compile.js | 19 | ||||
-rw-r--r-- | src/ide.js | 324 | ||||
-rw-r--r-- | src/index.js | 226 | ||||
-rw-r--r-- | src/to-javascript.js | 12 |
6 files changed, 357 insertions, 247 deletions
diff --git a/package.json b/package.json index ca5e5c8..1a1cc7e 100644 --- a/package.json +++ b/package.json | |||
@@ -39,7 +39,7 @@ | |||
39 | "chalk": "^1.1.3", | 39 | "chalk": "^1.1.3", |
40 | "cross-spawn": "^3.0.1", | 40 | "cross-spawn": "^3.0.1", |
41 | "dargs": "^5.1.0", | 41 | "dargs": "^5.1.0", |
42 | "debug": "^2.2.0", | 42 | "debug": "^2.6.0", |
43 | "globby": "^4.0.0", | 43 | "globby": "^4.0.0", |
44 | "js-string-escape": "^1.0.1", | 44 | "js-string-escape": "^1.0.1", |
45 | "lodash.difference": "^4.5.0", | 45 | "lodash.difference": "^4.5.0", |
diff --git a/src/bundle.js b/src/bundle.js index 6627ffe..3f55f01 100644 --- a/src/bundle.js +++ b/src/bundle.js | |||
@@ -12,12 +12,10 @@ const debug = require('debug')('purs-loader'); | |||
12 | 12 | ||
13 | const dargs = require('./dargs'); | 13 | const dargs = require('./dargs'); |
14 | 14 | ||
15 | module.exports = function bundle(options, cache) { | 15 | module.exports = function bundle(options, bundleModules) { |
16 | if (cache.bundle) return Promise.resolve(cache.bundle) | ||
17 | |||
18 | const stdout = [] | 16 | const stdout = [] |
19 | 17 | ||
20 | const stderr = cache.bundle = [] | 18 | const stderr = [] |
21 | 19 | ||
22 | const bundleCommand = options.pscBundle || 'purs'; | 20 | const bundleCommand = options.pscBundle || 'purs'; |
23 | 21 | ||
@@ -27,9 +25,9 @@ module.exports = function bundle(options, cache) { | |||
27 | namespace: options.bundleNamespace, | 25 | namespace: options.bundleNamespace, |
28 | }, options.pscBundleArgs))); | 26 | }, options.pscBundleArgs))); |
29 | 27 | ||
30 | cache.bundleModules.forEach(name => bundleArgs.push('--module', name)) | 28 | bundleModules.forEach(name => bundleArgs.push('--module', name)) |
31 | 29 | ||
32 | debug('spawning bundler %s %o', bundleCommand, bundleArgs); | 30 | debug('bundle: %s %o', bundleCommand, bundleArgs); |
33 | 31 | ||
34 | return (new Promise((resolve, reject) => { | 32 | return (new Promise((resolve, reject) => { |
35 | debug('bundling PureScript...') | 33 | debug('bundling PureScript...') |
@@ -45,15 +43,16 @@ module.exports = function bundle(options, cache) { | |||
45 | 43 | ||
46 | if (code !== 0) { | 44 | if (code !== 0) { |
47 | const errorMessage = stderr.join(''); | 45 | const errorMessage = stderr.join(''); |
46 | |||
48 | if (errorMessage.length) { | 47 | if (errorMessage.length) { |
49 | psModule.emitError(errorMessage); | 48 | psModule.emitError(errorMessage); |
50 | } | 49 | } |
51 | return reject(new Error('bundling failed')) | ||
52 | } | ||
53 | 50 | ||
54 | cache.bundle = stderr | 51 | reject(new Error('bundling failed')) |
55 | 52 | } | |
56 | resolve(fs.appendFileAsync(options.bundleOutput, `module.exports = ${options.bundleNamespace}`)) | 53 | else { |
54 | resolve(fs.appendFileAsync(options.bundleOutput, `module.exports = ${options.bundleNamespace}`)) | ||
55 | } | ||
57 | }) | 56 | }) |
58 | })) | 57 | })) |
59 | }; | 58 | }; |
diff --git a/src/compile.js b/src/compile.js index 8b5d87f..707605c 100644 --- a/src/compile.js +++ b/src/compile.js | |||
@@ -4,17 +4,17 @@ const Promise = require('bluebird'); | |||
4 | 4 | ||
5 | const spawn = require('cross-spawn'); | 5 | const spawn = require('cross-spawn'); |
6 | 6 | ||
7 | const debug = require('debug')('purs-loader'); | 7 | const debug_ = require('debug'); |
8 | |||
9 | const debug = debug_('purs-loader'); | ||
10 | |||
11 | const debugVerbose = debug_('purs-loader:verbose'); | ||
8 | 12 | ||
9 | const dargs = require('./dargs'); | 13 | const dargs = require('./dargs'); |
10 | 14 | ||
11 | module.exports = function compile(psModule) { | 15 | module.exports = function compile(psModule) { |
12 | const options = psModule.options | 16 | const options = psModule.options |
13 | 17 | ||
14 | const cache = psModule.cache | ||
15 | |||
16 | const stderr = [] | ||
17 | |||
18 | const compileCommand = options.psc || 'purs'; | 18 | const compileCommand = options.psc || 'purs'; |
19 | 19 | ||
20 | const compileArgs = (options.psc ? [] : [ 'compile' ]).concat(dargs(Object.assign({ | 20 | const compileArgs = (options.psc ? [] : [ 'compile' ]).concat(dargs(Object.assign({ |
@@ -22,7 +22,9 @@ module.exports = function compile(psModule) { | |||
22 | output: options.output, | 22 | output: options.output, |
23 | }, options.pscArgs))) | 23 | }, options.pscArgs))) |
24 | 24 | ||
25 | debug('spawning compiler %s %o', compileCommand, compileArgs) | 25 | const stderr = []; |
26 | |||
27 | debug('compile %s %o', compileCommand, compileArgs) | ||
26 | 28 | ||
27 | return new Promise((resolve, reject) => { | 29 | return new Promise((resolve, reject) => { |
28 | debug('compiling PureScript...') | 30 | debug('compiling PureScript...') |
@@ -33,8 +35,13 @@ module.exports = function compile(psModule) { | |||
33 | stderr.push(data.toString()); | 35 | stderr.push(data.toString()); |
34 | }); | 36 | }); |
35 | 37 | ||
38 | compilation.stdout.on('data', data => { | ||
39 | debugVerbose(data.toString()); | ||
40 | }); | ||
41 | |||
36 | compilation.on('close', code => { | 42 | compilation.on('close', code => { |
37 | debug('finished compiling PureScript.') | 43 | debug('finished compiling PureScript.') |
44 | |||
38 | if (code !== 0) { | 45 | if (code !== 0) { |
39 | const errorMessage = stderr.join(''); | 46 | const errorMessage = stderr.join(''); |
40 | if (errorMessage.length) { | 47 | if (errorMessage.length) { |
@@ -12,193 +12,71 @@ const spawn = require('cross-spawn'); | |||
12 | 12 | ||
13 | const colors = require('chalk'); | 13 | const colors = require('chalk'); |
14 | 14 | ||
15 | const debug = require('debug')('purs-loader'); | 15 | const debug_ = require('debug'); |
16 | 16 | ||
17 | const dargs = require('./dargs'); | 17 | const debug = debug_('purs-loader'); |
18 | |||
19 | const Psc = require('./Psc'); | ||
20 | |||
21 | const PsModuleMap = require('./PsModuleMap'); | ||
22 | |||
23 | module.exports.connect = function connect(psModule) { | ||
24 | const options = psModule.options | ||
25 | const cache = psModule.cache | ||
26 | |||
27 | if (cache.ideServer) return Promise.resolve(psModule) | ||
28 | 18 | ||
29 | cache.ideServer = true | 19 | const debugVerbose = debug_('purs-loader:verbose'); |
30 | 20 | ||
31 | const connect_ = () => new Promise((resolve, reject) => { | 21 | const dargs = require('./dargs'); |
32 | const args = dargs(options.pscIdeArgs) | ||
33 | |||
34 | debug('attempting to run purs ide client: %o', args) | ||
35 | 22 | ||
36 | const ideClient = spawn('purs', ['ide', 'client'].concat(args)) | 23 | const compile = require('./compile'); |
37 | 24 | ||
38 | ideClient.stderr.on('data', data => { | 25 | const PsModuleMap = require('./PsModuleMap'); |
39 | debug(data.toString()) | ||
40 | cache.ideServer = false | ||
41 | reject(new Error('purs ide client failed')) | ||
42 | }) | ||
43 | ideClient.stdout.once('data', data => { | ||
44 | debug(data.toString()) | ||
45 | if (data.toString()[0] === '{') { | ||
46 | const res = JSON.parse(data.toString()) | ||
47 | if (res.resultType === 'success') { | ||
48 | cache.ideServer = ideServer | ||
49 | resolve(psModule) | ||
50 | } else { | ||
51 | cache.ideServer = ideServer | ||
52 | reject(new Error('purs ide client failed')) | ||
53 | } | ||
54 | } else { | ||
55 | cache.ideServer = false | ||
56 | reject(new Error('purs ide client failed')) | ||
57 | } | ||
58 | }) | ||
59 | ideClient.stdin.resume() | ||
60 | ideClient.stdin.write(JSON.stringify({ command: 'load' })) | ||
61 | ideClient.stdin.write('\n') | ||
62 | }) | ||
63 | 26 | ||
64 | const serverArgs = dargs(Object.assign({ | 27 | function UnknownModuleError() { |
65 | outputDirectory: options.output, | 28 | this.name = 'UnknownModuleError'; |
66 | '_': options.src | 29 | this.stack = (new Error()).stack; |
67 | }, options.pscIdeServerArgs)) | 30 | } |
68 | 31 | ||
69 | debug('attempting to start purs ide server: %o', serverArgs) | 32 | UnknownModuleError.prototype = Object.create(Error.prototype); |
70 | 33 | ||
71 | const ideServer = cache.ideServer = spawn('purs', ['ide', 'server'].concat(serverArgs)) | 34 | UnknownModuleError.prototype.constructor = UnknownModuleError; |
72 | 35 | ||
73 | ideServer.stdout.on('data', data => { | 36 | module.exports.UnknownModuleError = UnknownModuleError; |
74 | debug('purs ide server stdout: %s', data.toString()); | ||
75 | }); | ||
76 | 37 | ||
77 | ideServer.stderr.on('data', data => { | 38 | function spawnIdeClient(body, options) { |
78 | debug('purs ide server stderr: %s', data.toString()); | 39 | const ideClientCommand = 'purs'; |
79 | }); | ||
80 | 40 | ||
81 | ideServer.on('error', error => { | 41 | const ideClientArgs = ['ide', 'client'].concat(dargs(options.pscIdeArgs)); |
82 | debug('purs ide server error: %o', error); | ||
83 | }); | ||
84 | 42 | ||
85 | ideServer.on('close', (code, signal) => { | 43 | const stderr = []; |
86 | debug('purs ide server close: %s %s', code, signal); | ||
87 | }); | ||
88 | 44 | ||
89 | return retryPromise((retry, number) => { | 45 | const stdout = []; |
90 | return connect_().catch(error => { | ||
91 | if (!cache.ideServer && number === 9) { | ||
92 | debug(error) | ||
93 | 46 | ||
94 | console.warn('Failed to connect to or start purs ide server. A full compilation will occur on rebuild'); | 47 | debug('ide client %s %o %o', ideClientCommand, ideClientArgs, body); |
95 | 48 | ||
96 | return Promise.resolve(psModule) | 49 | return new Promise((resolve, reject) => { |
97 | } | 50 | const ideClient = spawn(ideClientCommand, ideClientArgs); |
98 | 51 | ||
99 | return retry(error) | 52 | ideClient.stderr.on('data', data => { |
53 | stderr.push(data.toString()); | ||
100 | }) | 54 | }) |
101 | }, { | ||
102 | retries: 9, | ||
103 | factor: 1, | ||
104 | minTimeout: 333, | ||
105 | maxTimeout: 333, | ||
106 | }) | ||
107 | }; | ||
108 | |||
109 | module.exports.rebuild = function rebuild(psModule) { | ||
110 | const options = psModule.options | ||
111 | const cache = psModule.cache | ||
112 | |||
113 | debug('attempting rebuild with purs ide client %s', psModule.srcPath) | ||
114 | |||
115 | const request = (body) => new Promise((resolve, reject) => { | ||
116 | const args = dargs(options.pscIdeArgs) | ||
117 | const ideClient = spawn('purs', ['ide', 'client'].concat(args)) | ||
118 | |||
119 | var stdout = '' | ||
120 | var stderr = '' | ||
121 | 55 | ||
122 | ideClient.stdout.on('data', data => { | 56 | ideClient.stdout.on('data', data => { |
123 | stdout = stdout + data.toString() | 57 | stdout.push(data.toString()); |
124 | }) | ||
125 | |||
126 | ideClient.stderr.on('data', data => { | ||
127 | stderr = stderr + data.toString() | ||
128 | }) | 58 | }) |
129 | 59 | ||
130 | ideClient.on('close', code => { | 60 | ideClient.on('close', code => { |
131 | if (code !== 0) { | 61 | if (code !== 0) { |
132 | const error = stderr === '' ? 'Failed to spawn purs ide client' : stderr | 62 | const errorMessage = stderr.join(''); |
133 | return reject(new Error(error)) | ||
134 | } | ||
135 | |||
136 | let res = null | ||
137 | 63 | ||
138 | try { | 64 | reject(new Error(`ide client failed: ${errorMessage}`)); |
139 | res = JSON.parse(stdout.toString()) | ||
140 | debug(res) | ||
141 | } catch (err) { | ||
142 | return reject(err) | ||
143 | } | 65 | } |
66 | else { | ||
67 | const result = stdout.join(''); | ||
144 | 68 | ||
145 | if (res && !Array.isArray(res.result)) { | 69 | resolve(result); |
146 | return resolve(psModule); | ||
147 | } | 70 | } |
148 | |||
149 | Promise.map(res.result, (item, i) => { | ||
150 | debug(item) | ||
151 | return formatIdeResult(item, options, i, res.result.length) | ||
152 | }) | ||
153 | .then(compileMessages => { | ||
154 | if (res.resultType === 'error') { | ||
155 | if (res.result.some(item => { | ||
156 | const isModuleNotFound = item.errorCode === 'ModuleNotFound'; | ||
157 | |||
158 | const isUnknownModule = item.errorCode === 'UnknownModule'; | ||
159 | |||
160 | const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); | ||
161 | |||
162 | return isModuleNotFound || isUnknownModule || isUnknownModuleImport; | ||
163 | })) { | ||
164 | debug('unknown module, attempting full recompile') | ||
165 | return Psc.compile(psModule) | ||
166 | .then(() => PsModuleMap.makeMap(options.src).then(map => { | ||
167 | debug('rebuilt module map after unknown module forced a recompile'); | ||
168 | cache.psModuleMap = map; | ||
169 | })) | ||
170 | .then(() => request({ command: 'load' })) | ||
171 | .then(resolve) | ||
172 | .catch(() => resolve(psModule)) | ||
173 | } | ||
174 | const errorMessage = compileMessages.join('\n'); | ||
175 | if (errorMessage.length) { | ||
176 | psModule.emitError(errorMessage); | ||
177 | } | ||
178 | resolve(psModule); | ||
179 | } else { | ||
180 | const warningMessage = compileMessages.join('\n'); | ||
181 | if (options.warnings && warningMessage.length) { | ||
182 | psModule.emitWarning(warningMessage); | ||
183 | } | ||
184 | resolve(psModule); | ||
185 | } | ||
186 | }) | ||
187 | }) | 71 | }) |
188 | 72 | ||
189 | debug('purs ide client stdin: %o', body); | 73 | ideClient.stdin.resume(); |
190 | 74 | ||
191 | ideClient.stdin.write(JSON.stringify(body)) | 75 | ideClient.stdin.write(JSON.stringify(body)); |
192 | ideClient.stdin.write('\n') | ||
193 | }) | ||
194 | 76 | ||
195 | return request({ | 77 | ideClient.stdin.write('\n'); |
196 | command: 'rebuild', | 78 | }); |
197 | params: { | 79 | } |
198 | file: psModule.srcPath, | ||
199 | } | ||
200 | }) | ||
201 | }; | ||
202 | 80 | ||
203 | function formatIdeResult(result, options, index, length) { | 81 | function formatIdeResult(result, options, index, length) { |
204 | let numAndErr = `[${index+1}/${length} ${result.errorCode}]` | 82 | let numAndErr = `[${index+1}/${length} ${result.errorCode}]` |
@@ -253,8 +131,140 @@ function formatIdeResult(result, options, index, length) { | |||
253 | } | 131 | } |
254 | 132 | ||
255 | return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) | 133 | return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) |
256 | }) | 134 | }).catch(error => { |
135 | debug('failed to format ide result: %o', error); | ||
136 | |||
137 | return Promise.resolve(''); | ||
138 | }); | ||
257 | } | 139 | } |
258 | 140 | ||
259 | return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); | 141 | return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); |
260 | } | 142 | } |
143 | |||
144 | module.exports.connect = function connect(psModule) { | ||
145 | const options = psModule.options | ||
146 | |||
147 | const serverCommand = 'purs'; | ||
148 | |||
149 | const serverArgs = ['ide', 'server'].concat(dargs(Object.assign({ | ||
150 | outputDirectory: options.output, | ||
151 | '_': options.src | ||
152 | }, options.pscIdeServerArgs))); | ||
153 | |||
154 | debug('ide server: %s %o', serverCommand, serverArgs); | ||
155 | |||
156 | const ideServer = spawn(serverCommand, serverArgs); | ||
157 | |||
158 | ideServer.stdout.on('data', data => { | ||
159 | debugVerbose('ide server stdout: %s', data.toString()); | ||
160 | }); | ||
161 | |||
162 | ideServer.stderr.on('data', data => { | ||
163 | debugVerbose('ide server stderr: %s', data.toString()); | ||
164 | }); | ||
165 | |||
166 | ideServer.on('error', error => { | ||
167 | debugVerbose('ide server error: %o', error); | ||
168 | }); | ||
169 | |||
170 | ideServer.on('close', (code, signal) => { | ||
171 | debugVerbose('ide server close: %s %s', code, signal); | ||
172 | }); | ||
173 | |||
174 | return Promise.resolve(ideServer); | ||
175 | }; | ||
176 | |||
177 | module.exports.load = function load(psModule) { | ||
178 | const options = psModule.options | ||
179 | |||
180 | const body = {command: 'load'}; | ||
181 | |||
182 | return spawnIdeClient(body, options); | ||
183 | }; | ||
184 | |||
185 | module.exports.loadWithRetry = function loadWithRetry(psModule) { | ||
186 | const retries = 9; | ||
187 | |||
188 | return retryPromise((retry, number) => { | ||
189 | debugVerbose('attempting to load modules (%d out of %d attempts)', number, retries); | ||
190 | |||
191 | return module.exports.load(psModule).catch(retry); | ||
192 | }, { | ||
193 | retries: retries, | ||
194 | factor: 1, | ||
195 | minTimeout: 333, | ||
196 | maxTimeout: 333, | ||
197 | }).then(() => psModule); | ||
198 | }; | ||
199 | |||
200 | module.exports.rebuild = function rebuild(psModule) { | ||
201 | const options = psModule.options; | ||
202 | |||
203 | const body = { | ||
204 | command: 'rebuild', | ||
205 | params: { | ||
206 | file: psModule.srcPath, | ||
207 | } | ||
208 | }; | ||
209 | |||
210 | const parseResponse = response => { | ||
211 | try { | ||
212 | const parsed = JSON.parse(response); | ||
213 | |||
214 | debugVerbose('parsed JSON response: %o', parsed); | ||
215 | |||
216 | return Promise.resolve(parsed); | ||
217 | } | ||
218 | catch (error) { | ||
219 | return Promise.reject(error); | ||
220 | } | ||
221 | }; | ||
222 | |||
223 | const formatResponse = parsed => { | ||
224 | const result = Array.isArray(parsed.result) ? parsed.result : []; | ||
225 | |||
226 | return Promise.map(result, (item, i) => { | ||
227 | debugVerbose('formatting result %o', item); | ||
228 | |||
229 | return formatIdeResult(item, options, i, result.length); | ||
230 | }).then(formatted => ({ | ||
231 | parsed: parsed, | ||
232 | formatted: formatted, | ||
233 | formattedMessage: formatted.join('\n') | ||
234 | })); | ||
235 | }; | ||
236 | |||
237 | return spawnIdeClient(body, options) | ||
238 | .then(parseResponse) | ||
239 | .then(formatResponse) | ||
240 | .then(({ parsed, formatted, formattedMessage }) => { | ||
241 | if (parsed.resultType === 'success') { | ||
242 | if (options.warnings && formattedMessage.length) { | ||
243 | psModule.emitWarning(formattedMessage); | ||
244 | } | ||
245 | |||
246 | return psModule; | ||
247 | } | ||
248 | else if ((parsed.result || []).some(item => { | ||
249 | const isModuleNotFound = item.errorCode === 'ModuleNotFound'; | ||
250 | |||
251 | const isUnknownModule = item.errorCode === 'UnknownModule'; | ||
252 | |||
253 | const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); | ||
254 | |||
255 | return isModuleNotFound || isUnknownModule || isUnknownModuleImport; | ||
256 | })) { | ||
257 | debug('failed to rebuild because the module is unknown') | ||
258 | |||
259 | return Promise.reject(new UnknownModuleError()); | ||
260 | } | ||
261 | else { | ||
262 | if (formattedMessage.length) { | ||
263 | psModule.emitError(formattedMessage); | ||
264 | } | ||
265 | |||
266 | return psModule; | ||
267 | } | ||
268 | }) | ||
269 | ; | ||
270 | }; | ||
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 | } |
diff --git a/src/to-javascript.js b/src/to-javascript.js index b402ad4..d0934d5 100644 --- a/src/to-javascript.js +++ b/src/to-javascript.js | |||
@@ -10,7 +10,11 @@ const jsStringEscape = require('js-string-escape'); | |||
10 | 10 | ||
11 | const difference = require('lodash.difference'); | 11 | const difference = require('lodash.difference'); |
12 | 12 | ||
13 | const debug = require('debug')('purs-loader'); | 13 | const debug_ = require('debug'); |
14 | |||
15 | const debug = debug_('purs-loader'); | ||
16 | |||
17 | const debugVerbose = debug_('purs-loader:verbose'); | ||
14 | 18 | ||
15 | const PsModuleMap = require('./PsModuleMap'); | 19 | const PsModuleMap = require('./PsModuleMap'); |
16 | 20 | ||
@@ -99,7 +103,7 @@ function makeJS(psModule, psModuleMap, js) { | |||
99 | const additionalImports = difference(imports, replacedImports); | 103 | const additionalImports = difference(imports, replacedImports); |
100 | 104 | ||
101 | if (additionalImports.length) { | 105 | if (additionalImports.length) { |
102 | debug('additional imports for %s: %o', name, additionalImports); | 106 | debugVerbose('additional imports for %s: %o', name, additionalImports); |
103 | } | 107 | } |
104 | 108 | ||
105 | const additionalImportsResult = additionalImports.map(import_ => { | 109 | const additionalImportsResult = additionalImports.map(import_ => { |
@@ -129,13 +133,13 @@ module.exports = function toJavaScript(psModule) { | |||
129 | 133 | ||
130 | const bundlePath = path.resolve(options.bundleOutput); | 134 | const bundlePath = path.resolve(options.bundleOutput); |
131 | 135 | ||
132 | const jsPath = cache.bundle ? bundlePath : psModule.jsPath; | 136 | const jsPath = options.bundle ? bundlePath : psModule.jsPath; |
133 | 137 | ||
134 | const js = fs.readFileAsync(jsPath, 'utf8').catch(() => ''); | 138 | const js = fs.readFileAsync(jsPath, 'utf8').catch(() => ''); |
135 | 139 | ||
136 | const psModuleMap = updatePsModuleMap(psModule); | 140 | const psModuleMap = updatePsModuleMap(psModule); |
137 | 141 | ||
138 | debug('loading JavaScript for %s', psModule.name); | 142 | debugVerbose('loading JavaScript for %s', psModule.name); |
139 | 143 | ||
140 | return Promise.props({js: js, psModuleMap: psModuleMap}).then(result => | 144 | return Promise.props({js: js, psModuleMap: psModuleMap}).then(result => |
141 | options.bundle ? | 145 | options.bundle ? |