diff options
Diffstat (limited to 'src/to-javascript.js')
-rw-r--r-- | src/to-javascript.js | 145 |
1 files changed, 145 insertions, 0 deletions
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 | }; | ||