Skip to content

Commit c8c36ca

Browse files
authored
Merge pull request #174 from static-dev/webpack2
convert to webpack2
2 parents 905350c + 3f6a943 commit c8c36ca

File tree

33 files changed

+1657
-1358
lines changed

33 files changed

+1657
-1358
lines changed

lib/config.js

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const path = require('path')
2-
const qs = require('querystring')
32
const Joi = require('joi')
43
const micromatch = require('micromatch')
54
const union = require('lodash.union')
@@ -28,7 +27,8 @@ module.exports = class Config {
2827
// merges API options into app.js options
2928
let allOpts = merge(this.parseAppJs(opts), opts)
3029
this.transformSpikeOptionsToWebpack(this.validateOpts(allOpts))
31-
this.project = project
30+
const sp = this.plugins.find((p) => p.name === 'spikePlugin')
31+
sp.options.project = project
3232
}
3333

3434
/**
@@ -56,13 +56,14 @@ module.exports = class Config {
5656
value: Joi.array().items(Joi.string()).single()
5757
}).default({ 'js/main': ['./assets/js/index.js'] }),
5858
vendor: Joi.array().single(),
59-
modulesDirectories: Joi.array().default(['node_modules', 'bower_components']),
6059
outputDir: Joi.string().default('public'),
60+
outputPublicPath: Joi.string(),
6161
plugins: Joi.array().default([]),
6262
afterSpikePlugins: Joi.array().default([]),
6363
module: Joi.object().default().keys({
64-
loaders: Joi.array().default([])
64+
rules: Joi.array().default([])
6565
}),
66+
devServer: Joi.func(),
6667
server: Joi.object().default().keys({
6768
watchOptions: Joi.object().default().keys({
6869
ignored: Joi.array().default('node_modules')
@@ -136,22 +137,15 @@ module.exports = class Config {
136137
return `!${path.join(p, i)}`
137138
}))
138139

139-
// parse any extra postcss options out to be passed as querystrings
140-
const postcssDirectKeys = ['plugins', 'parser', 'stringifier', 'syntax']
141-
res.postcssQuery = {}
142-
for (const k in res.postcss) {
143-
if (postcssDirectKeys.indexOf(k) < 0) res.postcssQuery[k] = res.postcss[k]
144-
}
145-
146140
// catch newly added files, put through the pipeline
147141
res.server.files = [{
148142
match: allWatchedFiles,
149143
fn: (event, file) => {
150144
const util = new SpikeUtils(this)
151145
const f = path.join(this.context, file.replace(p, ''))
152-
const files = this.spike.files.all
153-
if (files.indexOf(f) < 0 && !util.isFileIgnored(f) && event !== 'addDir') {
154-
this.project.watcher.watch([], [], [f])
146+
const opts = util.getSpikeOptions()
147+
if (opts.files.all.indexOf(f) < 0 && !util.isFileIgnored(f) && event !== 'addDir') {
148+
opts.project.watcher.watch([], [], [f])
155149
}
156150
}
157151
}]
@@ -167,20 +161,20 @@ module.exports = class Config {
167161
*/
168162
transformSpikeOptionsToWebpack (opts) {
169163
// `disallow` options would break spike if modified.
170-
const disallow = ['output', 'resolveLoader', 'spike', 'plugins', 'context']
164+
const disallow = ['output', 'resolveLoader', 'spike', 'plugins', 'afterSpikePlugins', 'context', 'outputPublicPath']
171165

172166
// `noCopy` options are spike-specific and shouldn't be directly added to
173167
// webpack's config
174-
const noCopy = ['root', 'matchers', 'env', 'server', 'cleanUrls', 'dumpDirs', 'ignore', 'vendor', 'outputDir', 'css', 'postcssQuery']
168+
const noCopy = ['root', 'matchers', 'env', 'server', 'cleanUrls', 'dumpDirs', 'ignore', 'vendor', 'outputDir', 'css', 'postcss', 'reshape', 'babel']
175169

176170
// All options other than `disallow` or `noCopy` are added directly to
177171
// webpack's config object
178172
const filteredOpts = removeKeys(opts, disallow.concat(noCopy))
179173
Object.assign(this, filteredOpts)
180174

181175
// `noCopy` options are added under the `spike` property
182-
this.spike = { files: {} }
183-
Object.assign(this.spike, filterKeys(opts, noCopy))
176+
const spike = { files: {} }
177+
Object.assign(spike, filterKeys(opts, noCopy))
184178

185179
// Now we run some spike-specific config transforms
186180
this.context = opts.root
@@ -190,9 +184,15 @@ module.exports = class Config {
190184
filename: '[name].js'
191185
}
192186

187+
// this is sometimes necessary for webpackjsonp loads in old browsers
188+
if (opts.outputPublicPath) {
189+
this.output.publicPath = opts.outputPublicPath
190+
}
191+
193192
this.resolveLoader = {
194-
root: [
193+
modules: [
195194
path.join(opts.root), // the project root
195+
path.join(opts.root, 'node_modules'), // the project node_modules
196196
path.join(__dirname, '../node_modules'), // spike/node_modules
197197
path.join(__dirname, '../../../node_modules') // spike's flattened deps, via npm 3+
198198
]
@@ -208,34 +208,45 @@ module.exports = class Config {
208208
const spikeLoaders = [
209209
{
210210
exclude: reIgnores,
211-
loader: `source-loader!postcss-loader?${qs.stringify(opts.postcssQuery)}`,
212-
_core: 'css'
211+
test: '/core!css',
212+
use: [
213+
{ loader: 'source-loader' },
214+
{ loader: 'postcss-loader', options: opts.postcss }
215+
]
213216
}, {
214217
exclude: reIgnores,
215-
loader: 'babel-loader',
216-
_core: 'js'
218+
test: '/core!js',
219+
use: [
220+
{ loader: 'babel-loader', options: opts.babel }
221+
]
217222
}, {
218223
exclude: reIgnores,
219-
loader: 'source-loader!reshape-loader',
220-
_core: 'html'
224+
test: '/core!html',
225+
use: [
226+
{ loader: 'source-loader' },
227+
{ loader: 'reshape-loader', options: opts.reshape }
228+
]
221229
}, {
222230
exclude: reIgnores,
223-
loader: 'source-loader',
224-
_core: 'static'
231+
test: '/core!static',
232+
use: [
233+
{ loader: 'source-loader' }
234+
]
225235
}
226236
]
227237

228-
this.module.loaders = spikeLoaders.concat(opts.module.loaders)
238+
this.module.rules = spikeLoaders.concat(opts.module.rules)
229239

230240
const util = new SpikeUtils(this)
241+
const spikePlugin = new SpikePlugin(util, spike)
231242

232243
this.plugins = [
233244
...opts.plugins,
234-
new SpikePlugin(util),
245+
spikePlugin,
235246
...opts.afterSpikePlugins,
236247
new BrowserSyncPlugin(opts.server, { callback: (_, bs) => {
237248
if (bs.utils.devIp.length) {
238-
this.project.emit('info', `External IP: http://${bs.utils.devIp[0]}:${this.spike.server.port}`)
249+
spike.project.emit('info', `External IP: http://${bs.utils.devIp[0]}:${spike.server.port}`)
239250
}
240251
} })
241252
]

lib/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const {Error, Warning} = require('./errors')
2222
* @classdesc Creates a spike project instance and allows interaction with it
2323
* @param {Object} opts - documented in the readme
2424
*/
25+
2526
class Spike extends EventEmitter {
2627
constructor (opts = {}) {
2728
super()
@@ -122,6 +123,9 @@ class Spike extends EventEmitter {
122123
questionnaire: opts.inquirer
123124
}
124125

126+
// TODO: remove this before release
127+
if (opts.template === 'base') sproutOptions.branch = 'webpack2'
128+
125129
// run it
126130
promise
127131
.tap(() => emit('info', 'initializing template'))
@@ -165,17 +169,17 @@ function npmInstall (opts) {
165169
*/
166170
function compileCallback (id, err, stats) {
167171
if (err) {
168-
return this.emit('error', new Error({ id: id, err: err }))
172+
return this.emit('error', new Error({ id, err }))
169173
}
170174
// Webpack "soft errors" are classified as warnings in spike. An error is
171175
// an error. If it doesn't break the build, it's a warning.
172176
const cstats = stats.compilation
173177
if (cstats.errors.length) {
174-
this.emit('warning', new Warning({ id: id, err: cstats.errors[0] }))
178+
this.emit('warning', new Warning({ id, err: cstats.errors[0] }))
175179
}
176180
/* istanbul ignore next */
177181
if (cstats.warnings.length) {
178-
this.emit('warning', new Warning({ id: id, err: cstats.warnings[0] }))
182+
this.emit('warning', new Warning({ id, err: cstats.warnings[0] }))
179183
}
180184

181185
this.emit('compile', {id, stats})

lib/plugin.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ const difference = require('lodash.difference')
1414
* the correct extension.
1515
*/
1616
module.exports = class SpikeWebpackPlugin {
17-
1817
/**
1918
* @constructor
2019
* @param {Object} opts - options for configuration
2120
* @param {String} opts.root - project root
2221
* @param {Array} opts.dumpDirs - directories to dump to public
2322
*/
24-
constructor (util) {
23+
constructor (util, opts) {
24+
this.name = 'spikePlugin'
2525
this.util = util
26+
this.options = opts
2627
}
2728

2829
/**
@@ -39,7 +40,7 @@ module.exports = class SpikeWebpackPlugin {
3940
// inject files into webpack's pipeline
4041
compiler.plugin('make', (compilation, done) => {
4142
// stores files to be processed by spike
42-
processedFiles = compiler.options.spike.files.process
43+
processedFiles = this.options.files.process
4344
filesOnly = processedFiles.map((f) => f.path)
4445
// see spike-util for details on this method
4546
this.util.addFilesAsWebpackEntries(compilation, filesOnly)
@@ -55,6 +56,12 @@ module.exports = class SpikeWebpackPlugin {
5556
if (el.userRequest === f.path) { return el }
5657
})
5758

59+
// if the dep isn't found, we have a bad loader issue
60+
// TODO: add a test for this
61+
if (!dep) {
62+
return new Error(`Module ${f.path} not found in webpack.\nMost likely, this is an issue with trying to add a custom loader. If you are trying to output your custom loader files as static, adding 'source-loader' should solve the issue. If you are trying to require them into your javascript, adding the _skipSpikeProcessing option to your loader config should solve the issue.`)
63+
}
64+
5865
// if we have an error in the module, return that
5966
if (dep.error) return done(dep.error)
6067

@@ -103,15 +110,28 @@ module.exports = class SpikeWebpackPlugin {
103110
* @param {Function} done - callback
104111
*/
105112
sortFiles (compiler, compilation, done) {
113+
// webpack is strict in its validation of loader props. we need to pass an
114+
// extra indentifier, so it is passed through the `test` prop, which we
115+
// re-assign here before it breaks anything!
116+
compiler.options.module.rules.map((rule) => {
117+
const match = typeof rule.test === 'string' && rule.test.match(/\/core!(\w+)/)
118+
if (match) {
119+
rule._core = match[1]
120+
delete rule.test
121+
}
122+
return rule
123+
})
124+
106125
// first we load up and categorize all the files in the project
107-
compiler.options.spike.files = this.getFilesFromGlob()
126+
this.options.files = this.getFilesFromGlob()
108127

109128
// now we set the core loaders' "test" properties to a very precise list of
110129
// files so that they only load the files we want
111-
compiler.options.module.loaders.map((l) => {
112-
if (!l._core) { return }
113-
l.test = pathToRegex(compiler.options.spike.files[l._core])
130+
compiler.options.module.rules.map((rule) => {
131+
if (!rule._core) { return }
132+
rule.test = pathToRegex(this.options.files[rule._core])
114133
})
134+
115135
done()
116136
}
117137

@@ -127,7 +147,7 @@ module.exports = class SpikeWebpackPlugin {
127147
// First, we grab all the files in the project, other than the ignored
128148
// files of course.
129149
const matcher = path.join(util.conf.context, '**/**')
130-
files.all = glob.sync(matcher, { ignore: util.conf.spike.ignore, dot: true, nodir: true })
150+
files.all = glob.sync(matcher, { ignore: this.options.ignore, dot: true, nodir: true })
131151

132152
// There are three special types of files we want to *not* be processed by
133153
// spike's core plugins. Here, we find these three types of files and push
@@ -140,8 +160,13 @@ module.exports = class SpikeWebpackPlugin {
140160
// First step, we find any files that are already being processed by a
141161
// user-added loader that has the `skipSpikeProcessing` key (we will refer
142162
// to these as "skipLoaders").
143-
const skipLoaderTests = util.conf.module.loaders.reduce((m, l) => {
144-
if (!l._core && l.skipSpikeProcessing) m.push(l.test); return m
163+
const skipLoaderTests = util.conf.module.rules.reduce((m, l) => {
164+
const skipInOpts = l.use.find((u) => u.options && u.options._skipSpikeProcessing)
165+
if (!l._core && l.use && skipInOpts) {
166+
m.push(l.test)
167+
delete skipInOpts.options._skipSpikeProcessing
168+
}
169+
return m
145170
}, [])
146171

147172
files.skipLoaders = allAvailableFiles.filter((f) => {
@@ -151,13 +176,13 @@ module.exports = class SpikeWebpackPlugin {
151176
allAvailableFiles = difference(allAvailableFiles, files.skipLoaders)
152177

153178
// Then we grab any files in the `vendored` config
154-
files.vendor = matchRelative.call(this, allAvailableFiles, util.conf.spike.vendor)
179+
files.vendor = matchRelative.call(this, allAvailableFiles, this.options.vendor)
155180

156181
allAvailableFiles = difference(allAvailableFiles, files.vendor)
157182

158183
// Finally, we pull any javascript files which are processed by webpack
159184
// internally using the 'js' matcher.
160-
files.js = matchRelative.call(this, allAvailableFiles, util.conf.spike.matchers.js)
185+
files.js = matchRelative.call(this, allAvailableFiles, this.options.matchers.js)
161186

162187
allAvailableFiles = difference(allAvailableFiles, files.js)
163188

@@ -175,9 +200,9 @@ module.exports = class SpikeWebpackPlugin {
175200
files.static = []
176201

177202
// We start with the core matchers
178-
for (const key in util.conf.spike.matchers) {
203+
for (const key in this.options.matchers) {
179204
if (key === 'js') continue // js files are handled by webpack
180-
const matcher = util.conf.spike.matchers[key]
205+
const matcher = this.options.matchers[key]
181206
const matchedFiles = matchRelative.call(this, allAvailableFiles, matcher)
182207

183208
// add to the matcher's category so it's handled by the correct loader
@@ -189,9 +214,16 @@ module.exports = class SpikeWebpackPlugin {
189214
}
190215

191216
// Then we add custom loaders with an 'extension' property
192-
util.conf.module.loaders.filter((l) => l.extension).map((l) => {
217+
util.conf.module.rules.reduce((m, r) => {
218+
const hasExt = r.use.find((u) => u.options && u.options._spikeExtension)
219+
if (!hasExt) return m
220+
const ext = hasExt.options._spikeExtension
221+
delete hasExt.options._spikeExtension
222+
m.push([r, ext])
223+
return m
224+
}, []).map(([r, ext]) => {
193225
const extensionLoaderMatches = allAvailableFiles.reduce((m, f) => {
194-
if (f.match(l.test)) m.push({ path: f, extension: l.extension })
226+
if (f.match(r.test)) m.push({ path: f, extension: ext })
195227
return m
196228
}, [])
197229
files.process.push(...extensionLoaderMatches)

0 commit comments

Comments
 (0)