]>
Commit | Line | Data |
---|---|---|
7de41f10 AM |
1 | 'use strict' |
2 | ||
7de41f10 AM |
3 | const debug = require('debug')('purs-loader') |
4 | const loaderUtils = require('loader-utils') | |
7de41f10 AM |
5 | const Promise = require('bluebird') |
6 | const fs = Promise.promisifyAll(require('fs')) | |
7de41f10 | 7 | const path = require('path') |
f8d292fb | 8 | const jsStringEscape = require('js-string-escape') |
531c751f | 9 | const PsModuleMap = require('./PsModuleMap'); |
10 | const Psc = require('./Psc'); | |
11 | const PscIde = require('./PscIde'); | |
12 | const dargs = require('./dargs'); | |
86e2b3d4 AD |
13 | const spawn = require('cross-spawn').sync |
14 | const eol = require('os').EOL | |
7de41f10 | 15 | |
7de41f10 AM |
16 | const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g |
17 | ||
18 | module.exports = function purescriptLoader(source, map) { | |
19 | const callback = this.async() | |
20 | const config = this.options | |
8bdff272 | 21 | const query = loaderUtils.getOptions(this) || {} |
7de41f10 AM |
22 | const webpackOptions = this.options.purescriptLoader || {} |
23 | ||
86e2b3d4 AD |
24 | const depsPaths = (pscPackage => { |
25 | if (pscPackage) { | |
26 | debug('calling psc-package...') | |
27 | ||
28 | return spawn('psc-package', ['sources']).stdout.toString().split(eol).filter(v => v != '') | |
29 | } | |
30 | else { | |
31 | return [ path.join('bower_components', 'purescript-*', 'src', '**', '*.purs') ] | |
32 | } | |
33 | }) | |
34 | ||
35 | let options = Object.assign(webpackOptions, query) | |
36 | ||
37 | const defaultDeps = depsPaths(options.pscPackage) | |
38 | const defaultOptions = { | |
7de41f10 AM |
39 | context: config.context, |
40 | psc: 'psc', | |
41 | pscArgs: {}, | |
42 | pscBundle: 'psc-bundle', | |
43 | pscBundleArgs: {}, | |
5163b5a2 | 44 | pscIde: false, |
86e2b3d4 | 45 | pscIdeColors: options.psc === 'psa', |
7de41f10 | 46 | pscIdeArgs: {}, |
86e2b3d4 | 47 | pscPackage: false, |
7de41f10 AM |
48 | bundleOutput: 'output/bundle.js', |
49 | bundleNamespace: 'PS', | |
50 | bundle: false, | |
51 | warnings: true, | |
52 | output: 'output', | |
53 | src: [ | |
54 | path.join('src', '**', '*.purs'), | |
86e2b3d4 | 55 | ...defaultDeps |
2b2207bd | 56 | ] |
86e2b3d4 | 57 | } |
7de41f10 AM |
58 | |
59 | this.cacheable && this.cacheable() | |
60 | ||
61 | let cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { | |
62 | rebuild: false, | |
63 | deferred: [], | |
b683b0b1 | 64 | bundleModules: [], |
65 | warnings: [], | |
66 | errors: [] | |
7de41f10 AM |
67 | } |
68 | ||
86e2b3d4 AD |
69 | if (options.pscPackage && options.src) { |
70 | options.src = options.src.concat(defaultDeps) // append psc-package-provided source paths with users' | |
71 | } | |
72 | ||
73 | options = Object.assign(defaultOptions, options) | |
74 | ||
7de41f10 AM |
75 | if (!config.purescriptLoaderInstalled) { |
76 | config.purescriptLoaderInstalled = true | |
77 | ||
78 | // invalidate loader cache when bundle is marked as invalid (in watch mode) | |
79 | this._compiler.plugin('invalid', () => { | |
531c751f | 80 | debug('invalidating loader cache'); |
81 | ||
7de41f10 | 82 | cache = config.purescriptLoaderCache = { |
5163b5a2 | 83 | rebuild: options.pscIde, |
7de41f10 | 84 | deferred: [], |
531c751f | 85 | bundleModules: [], |
86 | ideServer: cache.ideServer, | |
b683b0b1 | 87 | psModuleMap: cache.psModuleMap, |
88 | warnings: [], | |
89 | errors: [] | |
7de41f10 | 90 | } |
b683b0b1 | 91 | }); |
92 | ||
93 | // add psc warnings to webpack compilation warnings | |
94 | this._compiler.plugin('after-compile', (compilation, callback) => { | |
95 | cache.warnings.forEach(warning => { | |
96 | compilation.warnings.push(warning); | |
97 | }); | |
98 | ||
99 | cache.errors.forEach(error => { | |
100 | compilation.errors.push(error); | |
101 | }); | |
102 | ||
103 | callback() | |
104 | }); | |
7de41f10 AM |
105 | } |
106 | ||
531c751f | 107 | const psModuleName = PsModuleMap.match(source) |
7de41f10 AM |
108 | const psModule = { |
109 | name: psModuleName, | |
110 | load: js => callback(null, js), | |
111 | reject: error => callback(error), | |
112 | srcPath: this.resourcePath, | |
113 | srcDir: path.dirname(this.resourcePath), | |
114 | jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')), | |
115 | options: options, | |
116 | cache: cache, | |
b683b0b1 | 117 | emitWarning: warning => { |
118 | if (options.warnings && warning.length) { | |
119 | cache.warnings.push(warning); | |
120 | } | |
121 | }, | |
122 | emitError: error => { | |
123 | if (error.length) { | |
124 | cache.errors.push(error); | |
125 | } | |
126 | } | |
7de41f10 AM |
127 | } |
128 | ||
67888496 AM |
129 | debug('loader called', psModule.name) |
130 | ||
7de41f10 AM |
131 | if (options.bundle) { |
132 | cache.bundleModules.push(psModule.name) | |
133 | } | |
134 | ||
135 | if (cache.rebuild) { | |
531c751f | 136 | return PscIde.connect(psModule) |
137 | .then(PscIde.rebuild) | |
7de41f10 AM |
138 | .then(toJavaScript) |
139 | .then(psModule.load) | |
140 | .catch(psModule.reject) | |
141 | } | |
142 | ||
67888496 | 143 | if (cache.compilationFinished) { |
7de41f10 AM |
144 | return toJavaScript(psModule).then(psModule.load).catch(psModule.reject) |
145 | } | |
146 | ||
147 | // We need to wait for compilation to finish before the loaders run so that | |
148 | // references to compiled output are valid. | |
149 | cache.deferred.push(psModule) | |
150 | ||
67888496 | 151 | if (!cache.compilationStarted) { |
531c751f | 152 | return Psc.compile(psModule) |
153 | .then(() => PsModuleMap.makeMap(options.src).then(map => { | |
154 | debug('rebuilt module map'); | |
155 | cache.psModuleMap = map; | |
156 | })) | |
7de41f10 AM |
157 | .then(() => Promise.map(cache.deferred, psModule => { |
158 | if (typeof cache.ideServer === 'object') cache.ideServer.kill() | |
159 | return toJavaScript(psModule).then(psModule.load) | |
160 | })) | |
161 | .catch(error => { | |
162 | cache.deferred[0].reject(error) | |
4b99e432 | 163 | cache.deferred.slice(1).forEach(psModule => psModule.reject(new Error('purs-loader failed'))) |
7de41f10 AM |
164 | }) |
165 | } | |
166 | } | |
167 | ||
531c751f | 168 | function updatePsModuleMap(psModule) { |
169 | const options = psModule.options | |
170 | const cache = psModule.cache | |
171 | const filePurs = psModule.srcPath | |
172 | if (!cache.psModuleMap) { | |
173 | debug('module mapping does not exist'); | |
174 | return PsModuleMap.makeMap(options.src).then(map => { | |
175 | cache.psModuleMap = map; | |
176 | return cache.psModuleMap; | |
177 | }); | |
178 | } | |
179 | else { | |
180 | return PsModuleMap.makeMapEntry(filePurs).then(result => { | |
181 | const map = Object.assign(cache.psModuleMap, result) | |
182 | cache.psModuleMap = map; | |
183 | return cache.psModuleMap; | |
184 | }); | |
185 | } | |
186 | } | |
187 | ||
7de41f10 AM |
188 | // The actual loader is executed *after* purescript compilation. |
189 | function toJavaScript(psModule) { | |
190 | const options = psModule.options | |
191 | const cache = psModule.cache | |
192 | const bundlePath = path.resolve(options.bundleOutput) | |
193 | const jsPath = cache.bundle ? bundlePath : psModule.jsPath | |
194 | ||
67888496 | 195 | debug('loading JavaScript for', psModule.name) |
7de41f10 AM |
196 | |
197 | return Promise.props({ | |
198 | js: fs.readFileAsync(jsPath, 'utf8'), | |
531c751f | 199 | psModuleMap: updatePsModuleMap(psModule) |
7de41f10 AM |
200 | }).then(result => { |
201 | let js = '' | |
202 | ||
203 | if (options.bundle) { | |
204 | // if bundling, return a reference to the bundle | |
205 | js = 'module.exports = require("' | |
f8d292fb | 206 | + jsStringEscape(path.relative(psModule.srcDir, options.bundleOutput)) |
7de41f10 AM |
207 | + '")["' + psModule.name + '"]' |
208 | } else { | |
209 | // replace require paths to output files generated by psc with paths | |
210 | // to purescript sources, which are then also run through this loader. | |
7de41f10 AM |
211 | js = result.js |
212 | .replace(requireRegex, (m, p1) => { | |
f8d292fb | 213 | return 'require("' + jsStringEscape(result.psModuleMap[p1].src) + '")' |
17acb575 AM |
214 | }) |
215 | .replace(/require\(['"]\.\/foreign['"]\)/g, (m, p1) => { | |
f8d292fb | 216 | return 'require("' + jsStringEscape(result.psModuleMap[psModule.name].ffi) + '")' |
7de41f10 | 217 | }) |
7de41f10 AM |
218 | } |
219 | ||
220 | return js | |
221 | }) | |
222 | } |