Webpack¶
Webpack is a powerfull tool. We split the configuration (found in config/webpack) in three different files: default.js, client.js and server.js.
Both client and server configurations extend default, and if more targets (cordova, electron, browser extensions, ...) were to be added to the project, a similar file structure could be kept.
The loaders configuration has been split, for better readability and code reuse.
Default configuration¶
Default configuration define the common chunks of configuration that will be used whatever the target platform is.
import path from 'path'
import config from '../index'
import loaders from './loaders'
const AUTOPREFIXER_BROWSERS = [
'Android 2.3',
'Android >= 4',
'Chrome >= 35',
'Firefox >= 31',
'Explorer >= 9',
'iOS >= 7',
'Opera >= 12',
'Safari >= 7.1'
]
const defaultWebpackConfig = {
// Input
context: path.resolve(__dirname, '../../src'),
// Output
output: {
path: path.resolve(__dirname, '../../build'),
publicPath: '/',
sourcePrefix: ' '
},
// Loaders
module: { loaders },
// is it the right place ? at least needed server side for HMR. Is it required if DEBUG == false?
recordsPath: path.resolve(__dirname, '../../build/_records'),
resolve: {
root: path.resolve(__dirname, '../../src'),
extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.json']
},
// Cache generated modules and chunks to improve performance for multiple incremental builds.
cache: config.DEBUG,
// Switch loaders to debug mode.
debug: config.DEBUG,
// TODO Use debug here to choose value?
devtool: 'source-map',
stats: {
colors: true,
reasons: config.DEBUG,
hash: config.VERBOSE,
version: config.VERBOSE,
timings: true,
chunks: config.VERBOSE,
chunkModules: config.VERBOSE,
cached: config.VERBOSE,
cachedAssets: config.VERBOSE
},
plugins: [],
sassLoader: {
includePaths: [path.resolve(__dirname, '../node_modules')]
},
/* eslint-disable global-require */
postcss (bundler) {
return [
require('postcss-import')({ addDependencyTo: bundler }),
require('precss')(),
require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS })
]
}
/* eslint-enable global-require */
}
export default defaultWebpackConfig
Client configuration¶
Client configuration (may be renamed to browser at some point, we’ll see) is in charge of transpiling the browser javascript so it can run in modern browsers.
import path from 'path'
import webpack from 'webpack'
import AssetsPlugin from 'assets-webpack-plugin'
import CompressionPlugin from 'compression-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import config from '../index'
import defaultConfig from './default'
const clientConfig = {
...defaultConfig,
entry: './client.js',
output: {
...defaultConfig.output,
path: path.join(defaultConfig.output.path, 'public/'),
filename: config.DEBUG ? '[name].js?[chunkhash]' : '[name].[chunkhash].js',
chunkFilename: config.DEBUG ? '[name].[id].js?[chunkhash]' : '[name].[id].[chunkhash].js'
},
plugins: [
...defaultConfig.plugins,
new webpack.DefinePlugin({ ...config, 'process.env.BROWSER': true }),
// Emit a file with assets paths
// https://github.com/sporto/assets-webpack-plugin#options
new AssetsPlugin({
path: path.resolve(__dirname, '../../build'),
filename: 'assets.js',
processOutput: (x) => `module.exports = ${JSON.stringify(x)};`
}),
// Add production plugins if we're doing an optimized build
...(!config.DEBUG ? [
new ExtractTextPlugin('[name].[chunkhash].css', { allChunks: true }),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
screw_ie8: true,
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
warnings: config.VERBOSE
}
}),
new webpack.optimize.AggressiveMergingPlugin(),
new CompressionPlugin()
] : [])
]
}
if (!config.DEBUG) {
// Production: Replace loaders with "ExtractTextPlugin"
const originalLoaders = clientConfig.module.loaders[1].loaders
delete clientConfig.module.loaders[1].loaders
clientConfig.module.loaders[1].loader = ExtractTextPlugin.extract(...originalLoaders)
}
export default clientConfig
Server configuration¶
Server configuration is in charge of transpiling code required to run the production-ready express server, to be directly run by the Node interpreter.
import webpack from 'webpack'
import config from '../index'
import defaultConfig from './default'
/**
* This is Webpack configuration for server side javascript, aimed for a build
* without intrumentation (like dev middleware or hot module reload).
*
* Instrumentation will be added by the runServer script, if needed.
*/
const serverConfig = {
// Extends common configuration
...defaultConfig,
// Entry point
entry: './server.js',
// Output a single server.js, under the build directory
output: {
...defaultConfig.output,
filename: 'server.js',
libraryTarget: 'commonjs2'
},
// This will be run by node. Also used by our scripts to detect it is
// the server-side configuration.
target: 'node',
// How do we take apart bundlable scripts and external dependencies that we
// can load from filesystem?
externals: [
/^\.\/assets$/,
function filter (context, request, cb) {
const isExternal = request.match(/^[@a-z][a-z\/\.\-0-9]*$/i)
cb(null, Boolean(isExternal))
}
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false
},
plugins: [
...defaultConfig.plugins,
new webpack.DefinePlugin({ ...config, 'process.env.BROWSER': false }),
new webpack.BannerPlugin('require("source-map-support").install();',
{ raw: true, entryOnly: false })
]
}
export default serverConfig
Loaders configuration¶
All loaders¶
import config from '../../index'
import javascriptLoader from './javascript'
import styleLoader from './style'
export default [
javascriptLoader,
styleLoader,
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.txt$/, loader: 'raw-loader' },
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader',
query: {
name: config.DEBUG ? '[path][name].[ext]?[hash]' : '[hash].[ext]',
limit: 10000
}
},
{
test: /\.(ttf|eot|wav|mp3)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
query: {
name: config.DEBUG ? '[path][name].[ext]?[hash]' : '[hash].[ext]'
}
}
]
Javascript¶
import config from '../../index'
import path from 'path'
export default {
test: /\.jsx?$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, '../../../src'),
path.resolve(__dirname, '../../../config'),
path.resolve(__dirname, '../../../test'),
path.resolve(__dirname, '../../../build/assets')
],
query: {
// https://github.com/babel/babel-loader#options
cacheDirectory: config.DEBUG,
// https://babeljs.io/docs/usage/options/
babelrc: false,
presets: [
'react',
'es2015',
'stage-0'
],
plugins: [
'transform-runtime',
...config.DEBUG ? [] : [
'transform-react-remove-prop-types',
'transform-react-constant-elements',
'transform-react-inline-elements'
]
]
}
}
Style¶
import config from '../../index'
export default {
test: /\.scss$/,
loaders: [
'style',
`css?${JSON.stringify({
// `sourceMap` is set to false because otherwise, there will be a problem with custom fonts
// when using the development proxy.
// See http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts
sourceMap: false,
// CSS Modules https://github.com/css-modules/css-modules
// modules: true,
localIdentName: config.DEBUG ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]',
// CSS Nano http://cssnano.co/options/
minimize: !config.DEBUG
})}!sass?${JSON.stringify({
sourceMap: config.DEBUG
})}`
]
}