UNPKG

10.8 kBJavaScriptView Raw
1import AssetsPlugin from 'assets-webpack-plugin'
2import chalk from 'chalk'
3import ExtractTextPlugin from 'extract-text-webpack-plugin'
4import fs from 'fs'
5// import NyanProgressPlugin from 'nyan-progress-webpack-plugin'
6import path from 'path'
7import preconditions from 'preconditions'
8import ProgressBarPlugin from 'progress-bar-webpack-plugin'
9import StatsPlugin from 'stats-webpack-plugin'
10import webpack from 'webpack'
11
12// let nyanProgressPlugin = new NyanProgressPlugin()
13let progressBarPlugin = new ProgressBarPlugin({
14 format: ' build [:bar] ' + chalk.green.bold(':percent') + ' (:elapsed seconds)',
15 clear: false
16})
17
18class WebpackConfigurationFactory {
19 constructor (options) {
20 let pc = preconditions.instance(options)
21 pc.shouldBeDefined('applicationName', 'applicationName is required.')
22 pc.shouldBeDefined('applicationHost', 'applicationHost is required.')
23 pc.shouldBeDefined('rootProjectPath', 'rootProjectPath is required.')
24 pc.shouldBeDefined('rootApplicationPath', 'rootApplicationPath is required.')
25 pc.shouldBeDefined('rootDeploymentPath', 'rootDeploymentPath is required.')
26 pc.shouldBeDefined('rootDeploymentApplicationPath', 'rootDeploymentApplicationPath is required.')
27 pc.shouldBeDefined('environment', 'environment is required.')
28 pc.shouldBeDefined('locales', 'locales is required.')
29 pc.shouldBeDefined('modules', 'modules is required')
30
31 this._options = options
32 this._applicationHost = options.applicationHost
33 this._applicationName = options.applicationName
34 this._rootProjectPath = options.rootProjectPath
35 this._rootApplicationPath = options.rootApplicationPath
36 this._rootDeploymentPath = options.rootDeploymentPath
37 this._rootDeploymentApplicationPath = options.rootDeploymentApplicationPath
38 this._environment = options.environment
39 }
40
41 createWebpackConfiguration () {
42 let client = this._createClientWebpackConfiguration()
43 let server = this._createServerWebpackConfiguration()
44 let config = [server, client]
45 return config
46 }
47
48 _createServerWebpackConfiguration () {
49 let options = this._options
50 let entry = this._buildServerEntry(options)
51 let output = this._buildServerOutput(options)
52 let plugins = this._buildServerPlugins(options)
53 let common = this._buildCommon()
54
55 let externalNodeModules = {}
56 let nodeModulePath = path.join(this._rootProjectPath, 'node_modules')
57 fs.readdirSync(nodeModulePath)
58 .filter(function (x) {
59 return ['.bin'].indexOf(x) === -1
60 })
61 .forEach(function (mod) {
62 if (mod !== 'react-toolbox') {
63 externalNodeModules[mod] = 'commonjs ' + mod
64 }
65 })
66
67 return {
68 name: 'server',
69 ...common,
70 entry: entry,
71 output: output,
72 plugins: plugins,
73 target: 'node',
74 devtool: 'sourcemap',
75 externals: externalNodeModules
76 }
77 }
78
79 _createClientWebpackConfiguration () {
80 let options = this._options
81 let entry = this._buildClientEntry(options)
82 let output = this._buildClientOutput(options)
83 let plugins = this._buildClientPlugins(options)
84 let common = this._buildCommon()
85
86 return {
87 name: 'client',
88 ...common,
89 entry: entry,
90 output: output,
91 plugins: plugins,
92 devtool: 'sourcemap'
93 }
94 }
95
96 _buildCommon () {
97 let options = this._options
98 let loaders = this._buildLoaders(options)
99 let applicationNamespace = path.join(this._rootProjectPath, 'applications', options.applicationName)
100 let modulesDirectories = [
101 'node_modules',
102 applicationNamespace
103 ]
104 this._options.modules.forEach((m) => {
105 let moduleNamespace = path.join(this._rootProjectPath, m)
106 modulesDirectories.push(moduleNamespace)
107 })
108 let config = {
109 resolve: {
110 modules: modulesDirectories,
111 extensions: ['*', '.js', '.jsx', '.scss', '.css', '.ts', '.tsx']
112 },
113 module: {
114 ...loaders
115 }
116 }
117 return config
118 }
119
120 _buildServerEntry (options) {
121 let entry = {
122 server: path.join(this._rootApplicationPath, 'server.jsx')
123 }
124 return entry
125 }
126
127 _buildClientEntry (options) {
128 let environment = this._options.environment
129 let clientEntry = []
130 if (environment === 'development') {
131 clientEntry.push('react-hot-loader/patch')
132 clientEntry.push('webpack-dev-server/client?http://localhost:' + this._options.devPort + '/')
133 }
134 clientEntry.push(path.join(this._rootApplicationPath, 'client.jsx'))
135 let entry = {
136 libs: [
137 'react',
138 'react-dom',
139 'react-router'
140 ],
141 client: clientEntry
142 }
143 return entry
144 }
145
146 _buildClientOutput (options) {
147 /*
148 * You can use [hash] like: path.join(this._rootApplicationPath, 'build', '[hash]')
149 * For now we will not use hash first, later on we will bring this to use when all is figured out
150 */
151 let output = {
152 path: path.join(this._rootApplicationPath, 'build', this._environment, 'client'),
153 publicPath: this._applicationHost + '/assets/',
154 filename: 'main.js',
155 chunkFilename: '[id].[name].js',
156 sourceMapFilename: '[file].map'
157 }
158
159 return output
160 }
161
162 _buildServerOutput (options) {
163 /*
164 * You can use [hash] like: path.join(this._rootApplicationPath, 'build', '[hash]')
165 * For now we will not use hash first, later on we will bring this to use when all is figured out
166 */
167 let output = {
168 path: path.join(this._rootApplicationPath, 'build', this._environment, 'server'),
169 publicPath: this._applicationHost + '/assets/',
170 filename: 'main.js',
171 chunkFilename: '[id].[name].js',
172 sourceMapFilename: '__dev/assets/server/debugging/[file].map'
173 }
174
175 return output
176 }
177
178 _buildLoaders (options) {
179 var loaders = {
180 'jsx': this._environment === 'development' // Disable react-hot-loader for now (it is causing bug in webpack-dev-server)
181 ? ['react-hot-loader/webpack', 'babel-loader?compact=false'] : ['babel-loader?compact=false'],
182 'js': 'babel-loader?compact=false',
183 'tsx': 'ts-loader',
184 'ts': 'ts-loader',
185 'json': 'json-loader',
186 'json5': 'json5-loader',
187 'txt': 'raw-loader',
188 'png|jpg|jpeg|gif|svg': 'url-loader?limit=10000',
189 'otf|woff|woff2': 'url-loader?limit=100000',
190 'ttf|eot': 'file-loader',
191 'wav|mp3': 'file-loader',
192 'html': 'html-loader',
193 'md|markdown': ['html-loader', 'markdown-loader']
194 }
195
196 var cssLoader = 'css-loader?module&localIdentName=[name]-[local]-[hash:base64:5]'
197
198 var stylesheetLoaders = {
199 'css': cssLoader,
200 'less': [cssLoader, 'less-loader'],
201 'styl': [cssLoader, 'stylus-loader'],
202 'scss|sass': [cssLoader, 'sass-loader']
203 }
204
205 Object.keys(stylesheetLoaders).forEach(function (ext) {
206 var stylesheetLoader = stylesheetLoaders[ext]
207 if (Array.isArray(stylesheetLoader)) {
208 stylesheetLoader = stylesheetLoader.join('!')
209 }
210 stylesheetLoaders[ext] = ExtractTextPlugin.extract({
211 fallback: 'style-loader',
212 use: stylesheetLoader
213 })
214 })
215
216 let resultLoaders = []
217 .concat(this._loadersByExtension(loaders))
218 .concat(this._loadersByExtension(stylesheetLoaders))
219
220 return {
221 loaders: resultLoaders
222 }
223 }
224
225 _buildServerPlugins (options) {
226 let plugins = []
227 // plugins.push(nyanProgressPlugin)
228
229 plugins.push(progressBarPlugin)
230 plugins.push(new StatsPlugin('stats.json', {
231 chunkModules: true
232 }))
233 plugins.push(new webpack.DefinePlugin(
234 {
235 '__PROJECT__': JSON.stringify(this._options),
236 '__SERVER__': true,
237 '__CLIENT__': false
238 }
239 ))
240 plugins.push(new ExtractTextPlugin('styles.css'))
241 plugins.push(new AssetsPlugin({
242 prettyPrint: true,
243 path: path.join(this._rootApplicationPath, 'build', this._environment, 'server'),
244 update: true
245 }))
246 plugins.push(new webpack.WatchIgnorePlugin([
247 /\.d\.ts$/
248 ]))
249 return plugins
250 }
251
252 _buildClientPlugins (options) {
253 let project = {
254 applicationName: this._options.applicationName,
255 applicationHost: this._options.applicationHost,
256 environment: this._options.environment,
257 locales: this._options.locales
258 }
259 let plugins = []
260 // plugins.push(nyanProgressPlugin)
261 plugins.push(progressBarPlugin)
262
263 if (this._options.environment === 'development') {
264 plugins.push(new webpack.PrefetchPlugin('react'))
265 plugins.push(new webpack.PrefetchPlugin('react-router'))
266 plugins.push(new webpack.PrefetchPlugin('react-dom/lib/ReactComponentBrowserEnvironment'))
267 plugins.push(new webpack.HotModuleReplacementPlugin())
268 }
269
270 plugins.push(new StatsPlugin('stats.json', {
271 chunkModules: true
272 }))
273
274 plugins.push(new webpack.optimize.CommonsChunkPlugin({
275 name: 'libs',
276 filename: 'commons.js',
277 minChunks: 0
278 }))
279
280 if (this._options.environment !== 'development') {
281 plugins.push(new webpack.optimize.UglifyJsPlugin(
282 { compressor: { warnings: false } }
283 ))
284 }
285
286 plugins.push(new webpack.DefinePlugin(
287 {
288 'process.env': {
289 'NODE_ENV': JSON.stringify(this._environment)
290 },
291 '__SERVER__': false,
292 '__CLIENT__': true,
293 '__PROJECT__': JSON.stringify(project)
294 }))
295 plugins.push(new webpack.NoEmitOnErrorsPlugin())
296 plugins.push(new ExtractTextPlugin('styles.css'))
297 plugins.push(new AssetsPlugin({
298 prettyPrint: true,
299 path: path.join(this._rootApplicationPath, 'build', this._environment, 'client'),
300 update: true
301 }))
302 plugins.push(new webpack.WatchIgnorePlugin([
303 /\.d\.ts$/
304 ]))
305 return plugins
306 }
307
308 _extsToRegExp (exts) {
309 return new RegExp('\\.(' + exts.map(function (ext) {
310 return ext.replace(/\./g, '\\.')
311 }).join('|') + ')(\\?.*)?$')
312 }
313
314 _loadersByExtension (obj) {
315 let self = this
316 var loaders = []
317 Object.keys(obj).forEach(function (key) {
318 var exts = key.split('|')
319 var value = obj[key]
320 var entry
321 if (exts.indexOf('js') >= 0) {
322 entry = {
323 exclude: [/node_modules/],
324 options: {
325 ignore: [/node_modules/]
326 },
327 test: self._extsToRegExp(exts)
328 }
329 } else {
330 entry = {
331 test: self._extsToRegExp(exts)
332 }
333 }
334
335 if (Array.isArray(value)) {
336 entry.loaders = value
337 } else if (typeof value === 'string') {
338 entry.loader = value
339 } else {
340 Object.keys(value).forEach(function (valueKey) {
341 entry[valueKey] = value[valueKey]
342 })
343 }
344 loaders.push(entry)
345 })
346 return loaders
347 };
348}
349
350export default WebpackConfigurationFactory