-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebpackConfigFactory.js
More file actions
295 lines (272 loc) · 11.1 KB
/
Copy pathwebpackConfigFactory.js
File metadata and controls
295 lines (272 loc) · 11.1 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/* eslint-disable no-console */
const path = require('path');
const webpack = require('webpack');
const AssetsPlugin = require('assets-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
// @see https://github.com/motdotla/dotenv
const dotenv = require('dotenv');
dotenv.config(process.env.NOW
// This is to support deployment to the "now" host. See the README for more info.
? { path: './.envnow', silent: true }
// Standard .env loading.
: { silent: true }
);
// :: [Any] -> [Any]
function removeEmpty(x) {
return x.filter(y => !!y);
}
// :: bool -> (Any, Any) -> Any
function ifElse(condition) {
return (then, or) => (condition ? then : or);
}
// :: ...Object -> Object
function merge() {
const funcArgs = Array.prototype.slice.call(arguments); // eslint-disable-line prefer-rest-params
return Object.assign.apply(
null,
removeEmpty([{}].concat(funcArgs))
);
}
function webpackConfigFactory({ target, mode }) {
if (!target || !~['client', 'server'].findIndex(valid => target === valid)) {
throw new Error(
'You must provide a "target" (client|server) to the webpackConfigFactory.'
);
}
if (!mode || !~['development', 'production'].findIndex(valid => mode === valid)) {
throw new Error(
'You must provide a "mode" (development|production) to the webpackConfigFactory.'
);
}
console.log(`==> ℹ️ Creating webpack "${target}" config in "${mode}" mode`);
const isDev = mode === 'development';
const isProd = mode === 'production';
const isClient = target === 'client';
const isServer = target === 'server';
const ifDev = ifElse(isDev);
const ifProd = ifElse(isProd);
const ifClient = ifElse(isClient);
const ifServer = ifElse(isServer);
const ifDevClient = ifElse(isDev && isClient);
const ifDevServer = ifElse(isDev && isServer);
const ifProdClient = ifElse(isProd && isClient);
return {
// We need to state that we are targetting "node" for our server bundle.
target: ifServer('node', 'web'),
// We have to set this to be able to use these items when executing a
// server bundle. Otherwise strangeness happens, like __dirname resolving
// to '/'. There is no effect on our client bundle.
node: {
__dirname: true,
__filename: true,
},
// cache: !(isDev && isServer),
// Anything listed in externals will not be included in our bundle.
externals: removeEmpty([
// We don't want our node_modules to be bundled with our server package,
// prefering them to be resolved via native node module system. Therefore
// we use the `webpack-node-externals` library to help us generate an
// externals config that will ignore all node_modules.
ifServer(nodeExternals()),
]),
devtool: ifElse(isServer || isDev)(
// We want to be able to get nice stack traces when running our server
// bundle. To fully support this we'll also need to configure the
// `node-source-map-support` module to execute at the start of the server
// bundle. This module will allow the node to make use of the
// source maps.
// We also want to be able to link to the source in chrome dev tools
// whilst we are in development mode. :)
'source-map',
// When in production client mode we don't want any source maps to
// decrease our payload sizes.
// This form has almost no cost.
'hidden-source-map'
),
// Define our entry chunks for our bundle.
entry: merge(
{
main: removeEmpty([
ifDevClient('react-hot-loader/patch'),
ifDevClient(`webpack-hot-middleware/client?reload=true&path=http://localhost:${process.env.CLIENT_DEVSERVER_PORT}/__webpack_hmr`),
path.resolve(__dirname, `./src/${target}/index.js`),
]),
},
ifClient({
// We create a seperate chunk containing our vendor modules. This can
// avoid unnecessary downloads by users as well as speed up development
// rebuild times by not having to rebundle everything with every change.
vendor: [
'isomorphic-fetch',
'lodash',
'react',
'react-dom',
'react-redux',
'react-router',
'redial',
'redux',
'redux-observable',
'rxjs',
'socket.io-client',
],
})
),
output: {
// The dir in which our bundle should be output.
path: path.resolve(__dirname, `./build/${target}`),
// The filename format for our bundle's entries.
filename: ifProdClient(
// We include a hash for client caching purposes. Including a unique
// has for every build will ensure browsers always fetch our newest
// bundle.
'[name]-[hash].js',
// We want a determinable file name when running our server bundles,
// as we need to be able to target our server start file from our
// npm scripts. We don't care about caching on the server anyway.
// We also want our client development builds to have a determinable
// name for our hot reloading client bundle server.
'[name].js'
),
chunkFilename: '[name]-[chunkhash].js',
// This is the web path under which our webpack bundled output should
// be considered as being served from. Useful in the client bundle context
// only.
publicPath: ifDevClient(
// As we run a seperate server for our client and server bundles we
// need to use an absolute http path for our assets public path.
`http://localhost:${process.env.CLIENT_DEVSERVER_PORT}/assets/`,
// Otherwise we expect our bundled output to be served from this path.
'/assets/'
),
// When in server mode we will output our bundle as a commonjs2 module.
libraryTarget: ifServer('commonjs2', 'var'),
},
resolve: {
// These extensions are tried when resolving a file.
extensions: ['.js', '.json'],
},
plugins: removeEmpty([
// Each key passed into DefinePlugin is an identifier.
// The values for each key will be inlined into the code replacing any
// instances of the keys that are found.
// If the value is a string it will be used as a code fragment.
// If the value isn’t a string, it will be stringified (including functions).
// If the value is an object all keys are removeEmpty the same way.
// If you prefix typeof to the key, it’s only removeEmpty for typeof calls.
new webpack.DefinePlugin({
'process.env': {
// NOTE: The NODE_ENV key is especially important for production
// builds as React relies on process.env.NODE_ENV for optimizations.
NODE_ENV: JSON.stringify(mode),
// All the below items match the config items in our .env file. Go
// to the .env_example for a description of each key.
SERVER_HOST: JSON.stringify(process.env.SERVER_HOST),
SERVER_PORT: JSON.stringify(process.env.SERVER_PORT),
CLIENT_DEVSERVER_PORT: JSON.stringify(process.env.CLIENT_DEVSERVER_PORT),
DISABLE_SSR: process.env.DISABLE_SSR,
WEBSITE_TITLE: JSON.stringify(process.env.WEBSITE_TITLE),
WEBSITE_DESCRIPTION: JSON.stringify(process.env.WEBSITE_DESCRIPTION),
},
}),
// Generates a JSON file containing a map of all the output files for
// our webpack bundle. A necessisty for our server rendering process
// as we need to interogate these files in order to know what JS/CSS
// we need to inject into our HTML.
new AssetsPlugin({
filename: 'assets.json',
path: path.resolve(__dirname, `./build/${target}`),
}),
// Ensures all our vendor bundle is a single file output and that any
// shared code between our main and vendor bundles are put into the vendor
// bundle.
ifClient(
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
})
),
// We don't want webpack errors to occur during development as it will
// kill our dev servers.
ifDev(new webpack.NoErrorsPlugin()),
// We need this plugin to enable hot module reloading for our dev server.
ifDevClient(new webpack.HotModuleReplacementPlugin()),
// Ensure only 1 file is output for the server bundles. This makes it
// much easer for us to clear the module cache when reloading the server.
ifDevServer(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 })),
// Adds options to all of our loaders.
ifProdClient(
new webpack.LoaderOptionsPlugin({
// Indicates to our loaders that they should minify their output
// if they have the capability to do so.
minimize: true,
// Indicates to our loaders that they should enter into debug mode
// should they support it.
debug: false,
})
),
// JS Minification.
ifProdClient(
new webpack.optimize.UglifyJsPlugin({
compress: {
screw_ie8: true,
warnings: false,
},
})
),
// Create smaller Lodash builds by replacing feature sets of modules with
// noop, identity, or simpler alternatives.
// @see https://github.com/lodash/lodash-webpack-plugin
// ifProdClient(new LodashModuleReplacementPlugin()),
ifProd(
// This is actually only useful when our deps are installed via npm2.
// In npm2 its possible to get duplicates of dependencies bundled
// given the nested module structure. npm3 is flat, so this doesn't
// occur.
new webpack.optimize.DedupePlugin()
),
]),
module: {
loaders: [
// Javascript
{
test: /\.js$/,
loader: 'babel-loader',
exclude: [/node_modules/, path.resolve(__dirname, './build')],
query: merge(
ifServer({
// We are running a node 6 server which has support for almost
// all of the ES2015 syntax, therefore we only transpile JSX.
presets: ['react'],
}),
ifClient({
// For our clients code we will need to transpile our JS into
// ES5 code for wider browser/device compatability.
presets: removeEmpty([
// JSX
'react',
// Webpack 2 includes support for es2015 imports, therefore we
// use this modified preset.
'es2015-webpack',
// Provides awesome react optimisations.
// @see https://github.com/thejameskyle/babel-react-optimize
ifProd('react-optimize'),
// Optimises our lodash bundle size.
// @see https://github.com/lodash/babel-plugin-lodash
// ifProd('lodash'),
// Needed for react hot loader v3
ifDev('react-hot-loader/babel'),
]),
})
),
},
// JSON
{
test: /\.json$/,
loader: 'json-loader',
},
],
},
};
}
module.exports = webpackConfigFactory;