aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authoreric thul <thul.eric@gmail.com>2017-02-12 18:05:07 -0500
committereric thul <thul.eric@gmail.com>2017-02-19 12:09:16 -0500
commit8e21ab0ab3f8ba9d129f1cf3b59f87d7a2b5bfc2 (patch)
tree0db1cffab8462d919299ab87eba42200748be632 /src
parenta3c358f80f8197d5a1d05e42916cd5593b5b2dd5 (diff)
downloadpurs-loader-8e21ab0ab3f8ba9d129f1cf3b59f87d7a2b5bfc2.tar.gz
purs-loader-8e21ab0ab3f8ba9d129f1cf3b59f87d7a2b5bfc2.tar.zst
purs-loader-8e21ab0ab3f8ba9d129f1cf3b59f87d7a2b5bfc2.zip
Ensure that all imported files are watched
In order to handle the case where a new PureScript file is imported, but fails to compile, the purs-loader now tracks imports for each PureScript file in order to append any additional imports to the resulting JS. This ensures that webpack will watch the new file even before it successfully compiles.
Diffstat (limited to 'src')
-rw-r--r--src/PsModuleMap.js22
-rw-r--r--src/index.js61
-rw-r--r--src/to-javascript.js129
3 files changed, 147 insertions, 65 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
9const globby = require('globby'); 9const globby = require('globby');
10 10
11const debug = require('debug')('purs-loader') 11const debug = require('debug')('purs-loader');
12 12
13const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i 13const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i;
14 14
15function match(str) { 15const importModuleRegex = /(?:^|\n)\s*import\s+([\w\.]+)/ig;
16
17function 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}
19module.exports.match = match; 21module.exports.match = matchModule;
22
23function matchImports(str) {
24 const matches = str.match(importModuleRegex);
25 return (matches || []).map(a => a.replace(/\n?\s*import\s+/i, ''));
26}
27module.exports.matchImports = matchImports;
20 28
21function makeMapEntry(filePurs) { 29function 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 }
diff --git a/src/index.js b/src/index.js
index 249f472..3f5e6a8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,18 +3,15 @@
3const debug = require('debug')('purs-loader') 3const debug = require('debug')('purs-loader')
4const loaderUtils = require('loader-utils') 4const loaderUtils = require('loader-utils')
5const Promise = require('bluebird') 5const Promise = require('bluebird')
6const fs = Promise.promisifyAll(require('fs'))
7const path = require('path') 6const path = require('path')
8const jsStringEscape = require('js-string-escape')
9const PsModuleMap = require('./PsModuleMap'); 7const PsModuleMap = require('./PsModuleMap');
10const Psc = require('./Psc'); 8const Psc = require('./Psc');
11const PscIde = require('./PscIde'); 9const PscIde = require('./PscIde');
10const toJavaScript = require('./to-javascript');
12const dargs = require('./dargs'); 11const dargs = require('./dargs');
13const spawn = require('cross-spawn').sync 12const spawn = require('cross-spawn').sync
14const eol = require('os').EOL 13const eol = require('os').EOL
15 14
16const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g
17
18module.exports = function purescriptLoader(source, map) { 15module.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
@@ -164,59 +161,3 @@ module.exports = function purescriptLoader(source, map) {
164 }) 161 })
165 } 162 }
166} 163}
167
168function 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.
189function 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..b0b9dda
--- /dev/null
+++ b/src/to-javascript.js
@@ -0,0 +1,129 @@
1'use strict';
2
3const Promise = require('bluebird');
4
5const fs = Promise.promisifyAll(require('fs'));
6
7const path = require('path');
8
9const jsStringEscape = require('js-string-escape');
10
11const difference = require('lodash.difference');
12
13const debug = require('debug')('purs-loader');
14
15const PsModuleMap = require('./PsModuleMap');
16
17function 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.
44function makeBundleJS(psModule) {
45 const bundleOutput = psMoudle.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.
64function 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 escapedPath = jsStringEscape(psModuleMap[p1].src);
78
79 replacedImports.push(p1);
80
81 return `require("${escapedPath}")`;
82 })
83 .replace(foreignRE, () => {
84 const escapedPath = jsStringEscape(psModuleMap[name].ffi);
85
86 return `require("${escapedPath}")`;
87 })
88 ;
89
90 debug('imports %o', imports);
91
92 debug('replaced imports %o', replacedImports);
93
94 const additionalImports = difference(imports, replacedImports);
95
96 debug('additional imports for %s: %o', name, additionalImports);
97
98 const additionalImportsResult = additionalImports.map(import_ => {
99 const escapedPath = jsStringEscape(psModuleMap[import_].src);
100
101 return `var ${import_.replace(/\./g, '_')} = require("${escapedPath}")`;
102 }).join('\n');
103
104 const result_ = result + (additionalImports.length ? '\n' + additionalImportsResult : '');
105
106 return result_;
107}
108
109module.exports = function toJavaScript(psModule) {
110 const options = psModule.options;
111
112 const cache = psModule.cache;
113
114 const bundlePath = path.resolve(options.bundleOutput);
115
116 const jsPath = cache.bundle ? bundlePath : psModule.jsPath;
117
118 const js = fs.readFileAsync(jsPath, 'utf8').catch(() => '');
119
120 const psModuleMap = updatePsModuleMap(psModule);
121
122 debug('loading JavaScript for %s', psModule.name);
123
124 return Promise.props({js: js, psModuleMap: psModuleMap}).then(result =>
125 options.bundle ?
126 makeBundleJS(psModule) :
127 makeJS(psModule, result.psModuleMap, result.js)
128 );
129};