]>
Commit | Line | Data |
---|---|---|
8e21ab0a | 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) { | |
75826060 | 45 | const bundleOutput = psModule.options.bundleOutput; |
8e21ab0a | 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) => { | |
4305f5b0 | 77 | const moduleValue = psModuleMap[p1]; |
8e21ab0a | 78 | |
4305f5b0 | 79 | if (!moduleValue) { |
80 | debug('module %s was not found in the map, replacing require with null', p1); | |
8e21ab0a | 81 | |
4305f5b0 | 82 | return 'null'; |
83 | } | |
84 | else { | |
85 | const escapedPath = jsStringEscape(moduleValue.src); | |
86 | ||
87 | replacedImports.push(p1); | |
88 | ||
89 | return `require("${escapedPath}")`; | |
90 | } | |
8e21ab0a | 91 | }) |
92 | .replace(foreignRE, () => { | |
93 | const escapedPath = jsStringEscape(psModuleMap[name].ffi); | |
94 | ||
95 | return `require("${escapedPath}")`; | |
96 | }) | |
97 | ; | |
98 | ||
8e21ab0a | 99 | const additionalImports = difference(imports, replacedImports); |
100 | ||
4305f5b0 | 101 | if (additionalImports.length) { |
102 | debug('additional imports for %s: %o', name, additionalImports); | |
103 | } | |
8e21ab0a | 104 | |
105 | const additionalImportsResult = additionalImports.map(import_ => { | |
09e09fb3 | 106 | const moduleValue = psModuleMap[import_]; |
8e21ab0a | 107 | |
09e09fb3 | 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'); | |
8e21ab0a | 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 | }; |