diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/PsModuleMap.js | 22 | ||||
-rw-r--r-- | src/Psc.js | 7 | ||||
-rw-r--r-- | src/PscIde.js | 27 | ||||
-rw-r--r-- | src/index.js | 64 | ||||
-rw-r--r-- | src/to-javascript.js | 145 |
5 files changed, 193 insertions, 72 deletions
diff --git a/src/PsModuleMap.js b/src/PsModuleMap.js index 2193f02..0ae687c 100644 --- a/src/PsModuleMap.js +++ b/src/PsModuleMap.js | |||
@@ -8,15 +8,23 @@ const fs = Promise.promisifyAll(require('fs')); | |||
8 | 8 | ||
9 | const globby = require('globby'); | 9 | const globby = require('globby'); |
10 | 10 | ||
11 | const debug = require('debug')('purs-loader') | 11 | const debug = require('debug')('purs-loader'); |
12 | 12 | ||
13 | const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i | 13 | const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i; |
14 | 14 | ||
15 | function match(str) { | 15 | const importModuleRegex = /(?:^|\n)\s*import\s+([\w\.]+)/ig; |
16 | |||
17 | function matchModule(str) { | ||
16 | const matches = str.match(srcModuleRegex); | 18 | const matches = str.match(srcModuleRegex); |
17 | return matches && matches[1]; | 19 | return matches && matches[1]; |
18 | } | 20 | } |
19 | module.exports.match = match; | 21 | module.exports.match = matchModule; |
22 | |||
23 | function matchImports(str) { | ||
24 | const matches = str.match(importModuleRegex); | ||
25 | return (matches || []).map(a => a.replace(/\n?\s*import\s+/i, '')); | ||
26 | } | ||
27 | module.exports.matchImports = matchImports; | ||
20 | 28 | ||
21 | function makeMapEntry(filePurs) { | 29 | function makeMapEntry(filePurs) { |
22 | const dirname = path.dirname(filePurs); | 30 | const dirname = path.dirname(filePurs); |
@@ -33,7 +41,9 @@ function makeMapEntry(filePurs) { | |||
33 | 41 | ||
34 | const sourceJs = fileMap.fileJs; | 42 | const sourceJs = fileMap.fileJs; |
35 | 43 | ||
36 | const moduleName = match(sourcePurs); | 44 | const moduleName = matchModule(sourcePurs); |
45 | |||
46 | const imports = matchImports(sourcePurs); | ||
37 | 47 | ||
38 | const map = {}; | 48 | const map = {}; |
39 | 49 | ||
@@ -41,6 +51,8 @@ function makeMapEntry(filePurs) { | |||
41 | 51 | ||
42 | map[moduleName].src = path.resolve(filePurs); | 52 | map[moduleName].src = path.resolve(filePurs); |
43 | 53 | ||
54 | map[moduleName].imports = imports; | ||
55 | |||
44 | if (sourceJs) { | 56 | if (sourceJs) { |
45 | map[moduleName].ffi = path.resolve(fileJs); | 57 | map[moduleName].ffi = path.resolve(fileJs); |
46 | } | 58 | } |
@@ -45,7 +45,12 @@ function compile(psModule) { | |||
45 | if (errorMessage.length) { | 45 | if (errorMessage.length) { |
46 | psModule.emitError(errorMessage); | 46 | psModule.emitError(errorMessage); |
47 | } | 47 | } |
48 | reject(new Error('compilation failed')) | 48 | if (options.watch) { |
49 | resolve(psModule); | ||
50 | } | ||
51 | else { | ||
52 | reject(new Error('compilation failed')) | ||
53 | } | ||
49 | } else { | 54 | } else { |
50 | const warningMessage = stderr.join(''); | 55 | const warningMessage = stderr.join(''); |
51 | if (options.warnings && warningMessage.length) { | 56 | if (options.warnings && warningMessage.length) { |
diff --git a/src/PscIde.js b/src/PscIde.js index 8a0e823..bf92b38 100644 --- a/src/PscIde.js +++ b/src/PscIde.js | |||
@@ -63,15 +63,28 @@ function connect(psModule) { | |||
63 | 63 | ||
64 | const serverArgs = dargs(Object.assign({ | 64 | const serverArgs = dargs(Object.assign({ |
65 | outputDirectory: options.output, | 65 | outputDirectory: options.output, |
66 | "_": options.src | 66 | '_': options.src |
67 | }, options.pscIdeServerArgs)) | 67 | }, options.pscIdeServerArgs)) |
68 | 68 | ||
69 | debug('attempting to start psc-ide-server', serverArgs) | 69 | debug('attempting to start psc-ide-server', serverArgs) |
70 | 70 | ||
71 | const ideServer = cache.ideServer = spawn('psc-ide-server', serverArgs) | 71 | const ideServer = cache.ideServer = spawn('psc-ide-server', serverArgs) |
72 | |||
73 | ideServer.stdout.on('data', data => { | ||
74 | debug('psc-ide-server stdout: %s', data.toString()); | ||
75 | }); | ||
76 | |||
72 | ideServer.stderr.on('data', data => { | 77 | ideServer.stderr.on('data', data => { |
73 | debug(data.toString()) | 78 | debug('psc-ide-server stderr: %s', data.toString()); |
74 | }) | 79 | }); |
80 | |||
81 | ideServer.on('error', error => { | ||
82 | debug('psc-ide-server error: %o', error); | ||
83 | }); | ||
84 | |||
85 | ideServer.on('close', (code, signal) => { | ||
86 | debug('psc-ide-server close: %s %s', code, signal); | ||
87 | }); | ||
75 | 88 | ||
76 | return retryPromise((retry, number) => { | 89 | return retryPromise((retry, number) => { |
77 | return connect_().catch(error => { | 90 | return connect_().catch(error => { |
@@ -141,16 +154,18 @@ function rebuild(psModule) { | |||
141 | .then(compileMessages => { | 154 | .then(compileMessages => { |
142 | if (res.resultType === 'error') { | 155 | if (res.resultType === 'error') { |
143 | if (res.result.some(item => { | 156 | if (res.result.some(item => { |
157 | const isModuleNotFound = item.errorCode === 'ModuleNotFound'; | ||
158 | |||
144 | const isUnknownModule = item.errorCode === 'UnknownModule'; | 159 | const isUnknownModule = item.errorCode === 'UnknownModule'; |
145 | 160 | ||
146 | const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); | 161 | const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); |
147 | 162 | ||
148 | return isUnknownModule || isUnknownModuleImport; | 163 | return isModuleNotFound || isUnknownModule || isUnknownModuleImport; |
149 | })) { | 164 | })) { |
150 | debug('unknown module, attempting full recompile') | 165 | debug('unknown module, attempting full recompile') |
151 | return Psc.compile(psModule) | 166 | return Psc.compile(psModule) |
152 | .then(() => PsModuleMap.makeMap(options.src).then(map => { | 167 | .then(() => PsModuleMap.makeMap(options.src).then(map => { |
153 | debug('rebuilt module map'); | 168 | debug('rebuilt module map after unknown module forced a recompile'); |
154 | cache.psModuleMap = map; | 169 | cache.psModuleMap = map; |
155 | })) | 170 | })) |
156 | .then(() => request({ command: 'load' })) | 171 | .then(() => request({ command: 'load' })) |
@@ -172,6 +187,8 @@ function rebuild(psModule) { | |||
172 | }) | 187 | }) |
173 | }) | 188 | }) |
174 | 189 | ||
190 | debug('psc-ide-client stdin: %o', body); | ||
191 | |||
175 | ideClient.stdin.write(JSON.stringify(body)) | 192 | ideClient.stdin.write(JSON.stringify(body)) |
176 | ideClient.stdin.write('\n') | 193 | ideClient.stdin.write('\n') |
177 | }) | 194 | }) |
diff --git a/src/index.js b/src/index.js index e49fd2c..047927c 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -3,18 +3,15 @@ | |||
3 | const debug = require('debug')('purs-loader') | 3 | const debug = require('debug')('purs-loader') |
4 | const loaderUtils = require('loader-utils') | 4 | const loaderUtils = require('loader-utils') |
5 | const Promise = require('bluebird') | 5 | const Promise = require('bluebird') |
6 | const fs = Promise.promisifyAll(require('fs')) | ||
7 | const path = require('path') | 6 | const path = require('path') |
8 | const jsStringEscape = require('js-string-escape') | ||
9 | const PsModuleMap = require('./PsModuleMap'); | 7 | const PsModuleMap = require('./PsModuleMap'); |
10 | const Psc = require('./Psc'); | 8 | const Psc = require('./Psc'); |
11 | const PscIde = require('./PscIde'); | 9 | const PscIde = require('./PscIde'); |
10 | const toJavaScript = require('./to-javascript'); | ||
12 | const dargs = require('./dargs'); | 11 | const dargs = require('./dargs'); |
13 | const spawn = require('cross-spawn').sync | 12 | const spawn = require('cross-spawn').sync |
14 | const eol = require('os').EOL | 13 | const eol = require('os').EOL |
15 | 14 | ||
16 | const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g | ||
17 | |||
18 | module.exports = function purescriptLoader(source, map) { | 15 | module.exports = function purescriptLoader(source, map) { |
19 | const callback = this.async() | 16 | const callback = this.async() |
20 | const config = this.options | 17 | const config = this.options |
@@ -49,6 +46,7 @@ module.exports = function purescriptLoader(source, map) { | |||
49 | bundleNamespace: 'PS', | 46 | bundleNamespace: 'PS', |
50 | bundle: false, | 47 | bundle: false, |
51 | warnings: true, | 48 | warnings: true, |
49 | watch: false, | ||
52 | output: 'output', | 50 | output: 'output', |
53 | src: [ | 51 | src: [ |
54 | path.join('src', '**', '*.purs'), | 52 | path.join('src', '**', '*.purs'), |
@@ -151,7 +149,7 @@ module.exports = function purescriptLoader(source, map) { | |||
151 | if (!cache.compilationStarted) { | 149 | if (!cache.compilationStarted) { |
152 | return Psc.compile(psModule) | 150 | return Psc.compile(psModule) |
153 | .then(() => PsModuleMap.makeMap(options.src).then(map => { | 151 | .then(() => PsModuleMap.makeMap(options.src).then(map => { |
154 | debug('rebuilt module map'); | 152 | debug('rebuilt module map after compile'); |
155 | cache.psModuleMap = map; | 153 | cache.psModuleMap = map; |
156 | })) | 154 | })) |
157 | .then(() => Promise.map(cache.deferred, psModule => { | 155 | .then(() => Promise.map(cache.deferred, psModule => { |
@@ -164,59 +162,3 @@ module.exports = function purescriptLoader(source, map) { | |||
164 | }) | 162 | }) |
165 | } | 163 | } |
166 | } | 164 | } |
167 | |||
168 | function updatePsModuleMap(psModule) { | ||
169 | const options = psModule.options | ||
170 | const cache = psModule.cache | ||
171 | const filePurs = psModule.srcPath | ||
172 | if (!cache.psModuleMap) { | ||
173 | debug('module mapping does not exist'); | ||
174 | return PsModuleMap.makeMap(options.src).then(map => { | ||
175 | cache.psModuleMap = map; | ||
176 | return cache.psModuleMap; | ||
177 | }); | ||
178 | } | ||
179 | else { | ||
180 | return PsModuleMap.makeMapEntry(filePurs).then(result => { | ||
181 | const map = Object.assign(cache.psModuleMap, result) | ||
182 | cache.psModuleMap = map; | ||
183 | return cache.psModuleMap; | ||
184 | }); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | // The actual loader is executed *after* purescript compilation. | ||
189 | function toJavaScript(psModule) { | ||
190 | const options = psModule.options | ||
191 | const cache = psModule.cache | ||
192 | const bundlePath = path.resolve(options.bundleOutput) | ||
193 | const jsPath = cache.bundle ? bundlePath : psModule.jsPath | ||
194 | |||
195 | debug('loading JavaScript for', psModule.name) | ||
196 | |||
197 | return Promise.props({ | ||
198 | js: fs.readFileAsync(jsPath, 'utf8'), | ||
199 | psModuleMap: updatePsModuleMap(psModule) | ||
200 | }).then(result => { | ||
201 | let js = '' | ||
202 | |||
203 | if (options.bundle) { | ||
204 | // if bundling, return a reference to the bundle | ||
205 | js = 'module.exports = require("' | ||
206 | + jsStringEscape(path.relative(psModule.srcDir, options.bundleOutput)) | ||
207 | + '")["' + psModule.name + '"]' | ||
208 | } else { | ||
209 | // replace require paths to output files generated by psc with paths | ||
210 | // to purescript sources, which are then also run through this loader. | ||
211 | js = result.js | ||
212 | .replace(requireRegex, (m, p1) => { | ||
213 | return 'require("' + jsStringEscape(result.psModuleMap[p1].src) + '")' | ||
214 | }) | ||
215 | .replace(/require\(['"]\.\/foreign['"]\)/g, (m, p1) => { | ||
216 | return 'require("' + jsStringEscape(result.psModuleMap[psModule.name].ffi) + '")' | ||
217 | }) | ||
218 | } | ||
219 | |||
220 | return js | ||
221 | }) | ||
222 | } | ||
diff --git a/src/to-javascript.js b/src/to-javascript.js new file mode 100644 index 0000000..b402ad4 --- /dev/null +++ b/src/to-javascript.js | |||
@@ -0,0 +1,145 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | const Promise = require('bluebird'); | ||
4 | |||
5 | const fs = Promise.promisifyAll(require('fs')); | ||
6 | |||
7 | const path = require('path'); | ||
8 | |||
9 | const jsStringEscape = require('js-string-escape'); | ||
10 | |||
11 | const difference = require('lodash.difference'); | ||
12 | |||
13 | const debug = require('debug')('purs-loader'); | ||
14 | |||
15 | const PsModuleMap = require('./PsModuleMap'); | ||
16 | |||
17 | function updatePsModuleMap(psModule) { | ||
18 | const options = psModule.options; | ||
19 | |||
20 | const cache = psModule.cache; | ||
21 | |||
22 | const filePurs = psModule.srcPath; | ||
23 | |||
24 | if (!cache.psModuleMap) { | ||
25 | debug('module mapping does not exist'); | ||
26 | |||
27 | return PsModuleMap.makeMap(options.src).then(map => { | ||
28 | cache.psModuleMap = map; | ||
29 | return cache.psModuleMap; | ||
30 | }); | ||
31 | } | ||
32 | else { | ||
33 | return PsModuleMap.makeMapEntry(filePurs).then(result => { | ||
34 | const map = Object.assign(cache.psModuleMap, result); | ||
35 | |||
36 | cache.psModuleMap = map; | ||
37 | |||
38 | return cache.psModuleMap; | ||
39 | }); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | // Reference the bundle. | ||
44 | function makeBundleJS(psModule) { | ||
45 | const bundleOutput = psModule.options.bundleOutput; | ||
46 | |||
47 | const name = psModule.name; | ||
48 | |||
49 | const srcDir = psModule.srcDir; | ||
50 | |||
51 | const escaped = jsStringEscape(path.relative(srcDir, bundleOutput)); | ||
52 | |||
53 | const result = `module.exports = require("${escaped}")["${name}"]`; | ||
54 | |||
55 | return result; | ||
56 | } | ||
57 | |||
58 | // Replace require paths to output files generated by psc with paths | ||
59 | // to purescript sources, which are then also run through this loader. | ||
60 | // Additionally, the imports replaced are tracked so that in the event | ||
61 | // the compiler fails to compile the PureScript source, we can tack on | ||
62 | // any new imports in order to allow webpack to watch the new files | ||
63 | // before they have been successfully compiled. | ||
64 | function makeJS(psModule, psModuleMap, js) { | ||
65 | const requireRE = /require\(['"]\.\.\/([\w\.]+)['"]\)/g; | ||
66 | |||
67 | const foreignRE = /require\(['"]\.\/foreign['"]\)/g; | ||
68 | |||
69 | const name = psModule.name; | ||
70 | |||
71 | const imports = psModuleMap[name].imports; | ||
72 | |||
73 | var replacedImports = []; | ||
74 | |||
75 | const result = js | ||
76 | .replace(requireRE, (m, p1) => { | ||
77 | const moduleValue = psModuleMap[p1]; | ||
78 | |||
79 | if (!moduleValue) { | ||
80 | debug('module %s was not found in the map, replacing require with null', p1); | ||
81 | |||
82 | return 'null'; | ||
83 | } | ||
84 | else { | ||
85 | const escapedPath = jsStringEscape(moduleValue.src); | ||
86 | |||
87 | replacedImports.push(p1); | ||
88 | |||
89 | return `require("${escapedPath}")`; | ||
90 | } | ||
91 | }) | ||
92 | .replace(foreignRE, () => { | ||
93 | const escapedPath = jsStringEscape(psModuleMap[name].ffi); | ||
94 | |||
95 | return `require("${escapedPath}")`; | ||
96 | }) | ||
97 | ; | ||
98 | |||
99 | const additionalImports = difference(imports, replacedImports); | ||
100 | |||
101 | if (additionalImports.length) { | ||
102 | debug('additional imports for %s: %o', name, additionalImports); | ||
103 | } | ||
104 | |||
105 | const additionalImportsResult = additionalImports.map(import_ => { | ||
106 | const moduleValue = psModuleMap[import_]; | ||
107 | |||
108 | if (!moduleValue) { | ||
109 | debug('module %s was not found in the map, skipping require', import_); | ||
110 | |||
111 | return null; | ||
112 | } | ||
113 | else { | ||
114 | const escapedPath = jsStringEscape(moduleValue.src); | ||
115 | |||
116 | return `var ${import_.replace(/\./g, '_')} = require("${escapedPath}")`; | ||
117 | } | ||
118 | }).filter(a => a !== null).join('\n'); | ||
119 | |||
120 | const result_ = result + (additionalImports.length ? '\n' + additionalImportsResult : ''); | ||
121 | |||
122 | return result_; | ||
123 | } | ||
124 | |||
125 | module.exports = function toJavaScript(psModule) { | ||
126 | const options = psModule.options; | ||
127 | |||
128 | const cache = psModule.cache; | ||
129 | |||
130 | const bundlePath = path.resolve(options.bundleOutput); | ||
131 | |||
132 | const jsPath = cache.bundle ? bundlePath : psModule.jsPath; | ||
133 | |||
134 | const js = fs.readFileAsync(jsPath, 'utf8').catch(() => ''); | ||
135 | |||
136 | const psModuleMap = updatePsModuleMap(psModule); | ||
137 | |||
138 | debug('loading JavaScript for %s', psModule.name); | ||
139 | |||
140 | return Promise.props({js: js, psModuleMap: psModuleMap}).then(result => | ||
141 | options.bundle ? | ||
142 | makeBundleJS(psModule) : | ||
143 | makeJS(psModule, result.psModuleMap, result.js) | ||
144 | ); | ||
145 | }; | ||