]>
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 | ||
7f0547d4 | 13 | const debug_ = require('debug'); |
14 | ||
15 | const debug = debug_('purs-loader'); | |
16 | ||
17 | const debugVerbose = debug_('purs-loader:verbose'); | |
8e21ab0a | 18 | |
6ccb09a5 | 19 | const PsModuleMap = require('./purs-module-map'); |
8e21ab0a | 20 | |
21 | function updatePsModuleMap(psModule) { | |
22 | const options = psModule.options; | |
23 | ||
24 | const cache = psModule.cache; | |
25 | ||
26 | const filePurs = psModule.srcPath; | |
27 | ||
28 | if (!cache.psModuleMap) { | |
a8410d6d | 29 | debugVerbose('module mapping does not exist - making a new module map'); |
8e21ab0a | 30 | |
a8410d6d | 31 | cache.psModuleMap = PsModuleMap.makeMap(options.src); |
32 | ||
33 | return cache.psModuleMap; | |
8e21ab0a | 34 | } |
35 | else { | |
a8410d6d | 36 | debugVerbose('module mapping exists - updating module map for %s', filePurs); |
37 | ||
38 | cache.psModuleMap = cache.psModuleMap.then(psModuleMap => | |
39 | PsModuleMap.makeMapEntry(filePurs).then(result => { | |
40 | const map = Object.assign(psModuleMap, result); | |
8e21ab0a | 41 | |
a8410d6d | 42 | return map; |
43 | }) | |
44 | ); | |
8e21ab0a | 45 | |
a8410d6d | 46 | return cache.psModuleMap; |
8e21ab0a | 47 | } |
48 | } | |
49 | ||
50 | // Reference the bundle. | |
51 | function makeBundleJS(psModule) { | |
75826060 | 52 | const bundleOutput = psModule.options.bundleOutput; |
8e21ab0a | 53 | |
54 | const name = psModule.name; | |
55 | ||
56 | const srcDir = psModule.srcDir; | |
57 | ||
58 | const escaped = jsStringEscape(path.relative(srcDir, bundleOutput)); | |
59 | ||
60 | const result = `module.exports = require("${escaped}")["${name}"]`; | |
61 | ||
94013ed7 | 62 | return Promise.resolve(result); |
8e21ab0a | 63 | } |
64 | ||
65 | // Replace require paths to output files generated by psc with paths | |
66 | // to purescript sources, which are then also run through this loader. | |
67 | // Additionally, the imports replaced are tracked so that in the event | |
68 | // the compiler fails to compile the PureScript source, we can tack on | |
69 | // any new imports in order to allow webpack to watch the new files | |
70 | // before they have been successfully compiled. | |
71 | function makeJS(psModule, psModuleMap, js) { | |
a2be98a0 | 72 | const requireRE = /require\(['"]\.\.\/([\w\.]+)(?:\/index\.js)?['"]\)/g; |
8e21ab0a | 73 | |
a2be98a0 | 74 | const foreignRE = /require\(['"]\.\/foreign(?:\.js)?['"]\)/g; |
8e21ab0a | 75 | |
76 | const name = psModule.name; | |
77 | ||
78 | const imports = psModuleMap[name].imports; | |
79 | ||
80 | var replacedImports = []; | |
81 | ||
82 | const result = js | |
83 | .replace(requireRE, (m, p1) => { | |
4305f5b0 | 84 | const moduleValue = psModuleMap[p1]; |
8e21ab0a | 85 | |
4305f5b0 | 86 | if (!moduleValue) { |
87 | debug('module %s was not found in the map, replacing require with null', p1); | |
8e21ab0a | 88 | |
4305f5b0 | 89 | return 'null'; |
90 | } | |
91 | else { | |
92 | const escapedPath = jsStringEscape(moduleValue.src); | |
93 | ||
94 | replacedImports.push(p1); | |
95 | ||
96 | return `require("${escapedPath}")`; | |
97 | } | |
8e21ab0a | 98 | }) |
99 | .replace(foreignRE, () => { | |
100 | const escapedPath = jsStringEscape(psModuleMap[name].ffi); | |
101 | ||
102 | return `require("${escapedPath}")`; | |
103 | }) | |
104 | ; | |
105 | ||
8e21ab0a | 106 | const additionalImports = difference(imports, replacedImports); |
107 | ||
94013ed7 | 108 | if (!additionalImports.length) { |
109 | return Promise.resolve(result); | |
4305f5b0 | 110 | } |
94013ed7 | 111 | else { |
1cdd37e8 CS |
112 | const missingImports = additionalImports.filter(moduleName => |
113 | !psModuleMap[moduleName] && moduleName.split('.')[0] !== 'Prim' | |
114 | ); | |
8e21ab0a | 115 | |
1cdd37e8 CS |
116 | let updatingPsModuleMap; |
117 | if (missingImports.length > 0) { | |
118 | debug('rebuilding module map due to missing imports for %s: %o', name, missingImports); | |
119 | psModule.cache.psModuleMap = null; | |
120 | updatingPsModuleMap = updatePsModuleMap(psModule); | |
121 | } else { | |
122 | updatingPsModuleMap = Promise.resolve(psModuleMap); | |
123 | } | |
124 | ||
125 | return updatingPsModuleMap.then(updatedPsModuleMap => { | |
126 | const missingImportsResult = missingImports.map(import_ => { | |
94013ed7 | 127 | const moduleValue = updatedPsModuleMap[import_]; |
09e09fb3 | 128 | |
94013ed7 | 129 | if (!moduleValue) { |
130 | debug('module %s was not found in the map, skipping require', import_); | |
09e09fb3 | 131 | |
94013ed7 | 132 | return null; |
133 | } | |
134 | else { | |
135 | const escapedPath = jsStringEscape(moduleValue.src); | |
8e21ab0a | 136 | |
94013ed7 | 137 | return `var ${import_.replace(/\./g, '_')} = require("${escapedPath}")`; |
138 | } | |
139 | }).filter(a => a !== null).join('\n'); | |
8e21ab0a | 140 | |
1cdd37e8 | 141 | return result + '\n' + missingImportsResult; |
94013ed7 | 142 | }); |
143 | } | |
8e21ab0a | 144 | } |
145 | ||
146 | module.exports = function toJavaScript(psModule) { | |
147 | const options = psModule.options; | |
148 | ||
149 | const cache = psModule.cache; | |
150 | ||
151 | const bundlePath = path.resolve(options.bundleOutput); | |
152 | ||
7f0547d4 | 153 | const jsPath = options.bundle ? bundlePath : psModule.jsPath; |
8e21ab0a | 154 | |
155 | const js = fs.readFileAsync(jsPath, 'utf8').catch(() => ''); | |
156 | ||
157 | const psModuleMap = updatePsModuleMap(psModule); | |
158 | ||
7f0547d4 | 159 | debugVerbose('loading JavaScript for %s', psModule.name); |
8e21ab0a | 160 | |
161 | return Promise.props({js: js, psModuleMap: psModuleMap}).then(result => | |
162 | options.bundle ? | |
163 | makeBundleJS(psModule) : | |
164 | makeJS(psModule, result.psModuleMap, result.js) | |
165 | ); | |
166 | }; |