3 const debug
= require('debug')('purs-loader')
4 const loaderUtils
= require('loader-utils')
5 const Promise
= require('bluebird')
6 const fs
= Promise
.promisifyAll(require('fs'))
7 const path
= require('path')
8 const jsStringEscape
= require('js-string-escape')
9 const PsModuleMap
= require('./PsModuleMap');
10 const Psc
= require('./Psc');
11 const PscIde
= require('./PscIde');
12 const dargs
= require('./dargs');
13 const spawn
= require('cross-spawn').sync
14 const eol
= require('os').EOL
16 const requireRegex
= /require\(['"]\.\.\/([\w\.]+)['"]\)/g
18 module
.exports
= function purescriptLoader(source
, map
) {
19 const callback
= this.async()
20 const config
= this.options
21 const query
= loaderUtils
.getOptions(this) || {}
22 const webpackOptions
= this.options
.purescriptLoader
|| {}
24 const depsPaths
= (pscPackage
=> {
26 debug('calling psc-package...')
28 return spawn('psc-package', ['sources']).stdout
.toString().split(eol
).filter(v
=> v
!= '')
31 return [ path
.join('bower_components', 'purescript-*', 'src', '**', '*.purs') ]
35 let options
= Object
.assign(webpackOptions
, query
)
37 const defaultDeps
= depsPaths(options
.pscPackage
)
38 const defaultOptions
= {
39 context: config
.context
,
42 pscBundle: 'psc-bundle',
45 pscIdeColors: options
.psc
=== 'psa',
48 bundleOutput: 'output/bundle.js',
49 bundleNamespace: 'PS',
54 path
.join('src', '**', '*.purs'),
59 this.cacheable
&& this.cacheable()
61 let cache
= config
.purescriptLoaderCache
= config
.purescriptLoaderCache
|| {
69 if (options
.pscPackage
&& options
.src
) {
70 options
.src
= options
.src
.concat(defaultDeps
) // append psc-package-provided source paths with users'
73 options
= Object
.assign(defaultOptions
, options
)
75 if (!config
.purescriptLoaderInstalled
) {
76 config
.purescriptLoaderInstalled
= true
78 // invalidate loader cache when bundle is marked as invalid (in watch mode)
79 this._compiler
.plugin('invalid', () => {
80 debug('invalidating loader cache');
82 cache
= config
.purescriptLoaderCache
= {
83 rebuild: options
.pscIde
,
86 ideServer: cache
.ideServer
,
87 psModuleMap: cache
.psModuleMap
,
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
);
99 cache
.errors
.forEach(error
=> {
100 compilation
.errors
.push(error
);
107 const psModuleName
= PsModuleMap
.match(source
)
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')),
117 emitWarning: warning
=> {
118 if (options
.warnings
&& warning
.length
) {
119 cache
.warnings
.push(warning
);
122 emitError: error
=> {
124 cache
.errors
.push(error
);
129 debug('loader called', psModule
.name
)
131 if (options
.bundle
) {
132 cache
.bundleModules
.push(psModule
.name
)
136 return PscIde
.connect(psModule
)
137 .then(PscIde
.rebuild
)
140 .catch(psModule
.reject
)
143 if (cache
.compilationFinished
) {
144 return toJavaScript(psModule
).then(psModule
.load
).catch(psModule
.reject
)
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
)
151 if (!cache
.compilationStarted
) {
152 return Psc
.compile(psModule
)
153 .then(() => PsModuleMap
.makeMap(options
.src
).then(map
=> {
154 debug('rebuilt module map');
155 cache
.psModuleMap
= map
;
157 .then(() => Promise
.map(cache
.deferred
, psModule
=> {
158 if (typeof cache
.ideServer
=== 'object') cache
.ideServer
.kill()
159 return toJavaScript(psModule
).then(psModule
.load
)
162 cache
.deferred
[0].reject(error
)
163 cache
.deferred
.slice(1).forEach(psModule
=> psModule
.reject(new Error('purs-loader failed')))
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
;
180 return PsModuleMap
.makeMapEntry(filePurs
).then(result
=> {
181 const map
= Object
.assign(cache
.psModuleMap
, result
)
182 cache
.psModuleMap
= map
;
183 return cache
.psModuleMap
;
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
195 debug('loading JavaScript for', psModule
.name
)
197 return Promise
.props({
198 js: fs
.readFileAsync(jsPath
, 'utf8'),
199 psModuleMap: updatePsModuleMap(psModule
)
203 if (options
.bundle
) {
204 // if bundling, return a reference to the bundle
205 js
= 'module.exports = require("'
206 + jsStringEscape(path
.relative(psModule
.srcDir
, options
.bundleOutput
))
207 + '")["' + psModule
.name
+ '"]'
209 // replace require paths to output files generated by psc with paths
210 // to purescript sources, which are then also run through this loader.
212 .replace(requireRegex
, (m
, p1
) => {
213 return 'require("' + jsStringEscape(result
.psModuleMap
[p1
].src
) + '")'
215 .replace(/require\(['"]\.\/foreign['"]\)/g, (m
, p1
) => {
216 return 'require("' + jsStringEscape(result
.psModuleMap
[psModule
.name
].ffi
) + '")'