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