]>
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 { |
112 | debug('rebuilding module map due to additional imports for %s: %o', name, additionalImports); | |
8e21ab0a | 113 | |
94013ed7 | 114 | psModule.cache.psModuleMap = null; |
8e21ab0a | 115 | |
94013ed7 | 116 | return updatePsModuleMap(psModule).then(updatedPsModuleMap => { |
117 | const additionalImportsResult = additionalImports.map(import_ => { | |
118 | const moduleValue = updatedPsModuleMap[import_]; | |
09e09fb3 | 119 | |
94013ed7 | 120 | if (!moduleValue) { |
121 | debug('module %s was not found in the map, skipping require', import_); | |
09e09fb3 | 122 | |
94013ed7 | 123 | return null; |
124 | } | |
125 | else { | |
126 | const escapedPath = jsStringEscape(moduleValue.src); | |
8e21ab0a | 127 | |
94013ed7 | 128 | return `var ${import_.replace(/\./g, '_')} = require("${escapedPath}")`; |
129 | } | |
130 | }).filter(a => a !== null).join('\n'); | |
8e21ab0a | 131 | |
94013ed7 | 132 | return result + '\n' + additionalImportsResult; |
133 | }); | |
134 | } | |
8e21ab0a | 135 | } |
136 | ||
137 | module.exports = function toJavaScript(psModule) { | |
138 | const options = psModule.options; | |
139 | ||
140 | const cache = psModule.cache; | |
141 | ||
142 | const bundlePath = path.resolve(options.bundleOutput); | |
143 | ||
7f0547d4 | 144 | const jsPath = options.bundle ? bundlePath : psModule.jsPath; |
8e21ab0a | 145 | |
146 | const js = fs.readFileAsync(jsPath, 'utf8').catch(() => ''); | |
147 | ||
148 | const psModuleMap = updatePsModuleMap(psModule); | |
149 | ||
7f0547d4 | 150 | debugVerbose('loading JavaScript for %s', psModule.name); |
8e21ab0a | 151 | |
152 | return Promise.props({js: js, psModuleMap: psModuleMap}).then(result => | |
153 | options.bundle ? | |
154 | makeBundleJS(psModule) : | |
155 | makeJS(psModule, result.psModuleMap, result.js) | |
156 | ); | |
157 | }; |