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