1 | import AssetsPlugin from 'assets-webpack-plugin'
|
2 | import chalk from 'chalk'
|
3 | import ExtractTextPlugin from 'extract-text-webpack-plugin'
|
4 | import fs from 'fs'
|
5 |
|
6 | import path from 'path'
|
7 | import preconditions from 'preconditions'
|
8 | import ProgressBarPlugin from 'progress-bar-webpack-plugin'
|
9 | import StatsPlugin from 'stats-webpack-plugin'
|
10 | import webpack from 'webpack'
|
11 |
|
12 |
|
13 | let progressBarPlugin = new ProgressBarPlugin({
|
14 | format: ' build [:bar] ' + chalk.green.bold(':percent') + ' (:elapsed seconds)',
|
15 | clear: false
|
16 | })
|
17 |
|
18 | class 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 |
|
149 |
|
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 |
|
165 |
|
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'
|
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 |
|
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 |
|
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 |
|
350 | export default WebpackConfigurationFactory
|