forked from cypress-io/cypress-webpack-preprocessor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
150 lines (124 loc) · 4.8 KB
/
index.js
File metadata and controls
150 lines (124 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
const path = require('path')
const webpack = require('webpack')
const log = require('debug')('cypress:webpack')
const createDeferred = require('./deferred')
const bundles = {}
// export a function that returns another function, making it easy for users
// to configure like so:
//
// on('file:preprocessor', webpack(options))
//
const preprocessor = (options = {}) => {
log('user options:', options)
// we return function that accepts the arguments provided by
// the event 'file:preprocessor'
//
// this function will get called for the support file when a project is loaded
// (if the support file is not disabled)
// it will also get called for a spec file when that spec is requested by
// the Cypress runner
//
// when running in the GUI, it will likely get called multiple times
// with the same filePath, as the user could re-run the tests, causing
// the supported file and spec file to be requested again
return (file) => {
const filePath = file.filePath
log('get', filePath)
// since this function can get called multiple times with the same
// filePath, we return the cached bundle promise if we already have one
// since we don't want or need to re-initiate webpack for it
if (bundles[filePath]) {
log(`already have bundle for ${filePath}`)
return bundles[filePath]
}
const watchOptions = options.watchOptions
// we're provided a default output path that lives alongside Cypress's
// app data files so we don't have to worry about where to put the bundled
// file on disk
const outputPath = file.outputPath
// we need to set entry and output
const webpackOptions = Object.assign({}, options.webpackOptions, {
entry: filePath,
output: {
path: path.dirname(outputPath),
filename: path.basename(outputPath),
},
})
log(`input: ${filePath}`)
log(`output: ${outputPath}`)
const compiler = webpack(webpackOptions)
// we keep a reference to the latest bundle in this scope
// it's a deferred object that will be resolved or rejected in
// the `handle` function below and its promise is what is ultimately
// returned from this function
let latestBundle = createDeferred()
// cache the bundle promise, so it can be returned if this function
// is invoked again with the same filePath
bundles[filePath] = latestBundle.promise
const rejectWithErr = (err) => {
err.filePath = filePath
// backup the original stack before it's potentially modified by bluebird
err.originalStack = err.stack
log(`errored bundling ${outputPath}`, err)
latestBundle.reject(err)
}
// this function is called when bundling is finished, once at the start
// and, if watching, each time watching triggers a re-bundle
const handle = (err, stats) => {
if (err) {
return rejectWithErr(err)
}
const jsonStats = stats.toJson()
if (stats.hasErrors()) {
err = new Error('Webpack Compilation Error')
err.stack = jsonStats.errors.join('\n\n')
return rejectWithErr(err)
}
// these stats are really only useful for debugging
if (jsonStats.warnings.length > 0) {
log(`warnings for ${outputPath}`)
log(jsonStats.warnings)
}
log('finished bundling', outputPath)
// resolve with the outputPath so Cypress knows where to serve
// the file from
latestBundle.resolve(outputPath)
}
// this event is triggered when watching and a file is saved
const plugin = { name: 'CypressWebpackPreprocessor' }
const onCompile = () => {
log('compile', filePath)
// we overwrite the latest bundle, so that a new call to this function
// returns a promise that resolves when the bundling is finished
latestBundle = createDeferred()
bundles[filePath] = latestBundle.promise.tap(() => {
log('- compile finished for', filePath)
// when the bundling is finished, we call `util.fileUpdated`
// to let Cypress know to re-run the spec
file.emit('rerun')
})
}
if (compiler.hooks) {
compiler.hooks.compile.tap(plugin, onCompile)
} else {
compiler.plugin('compile', onCompile)
}
if (file.shouldWatch) {
log('watching')
}
const bundler = file.shouldWatch ? compiler.watch(watchOptions, handle) : compiler.run(handle)
// when the spec or project is closed, we need to clean up the cached
// bundle promise and stop the watcher via `bundler.close()`
file.on('close', () => {
log('close', filePath)
delete bundles[filePath]
if (file.shouldWatch) {
bundler.close()
}
})
// return the promise, which will resolve with the outputPath or reject
// with any error encountered
return bundles[filePath]
}
}
module.exports = preprocessor