Skip to content

Instantly share code, notes, and snippets.

@SleeplessByte
Created April 25, 2017 00:42
Show Gist options
  • Save SleeplessByte/fe58d854d4a1787c188ce8bf098b6f0f to your computer and use it in GitHub Desktop.
Save SleeplessByte/fe58d854d4a1787c188ce8bf098b6f0f to your computer and use it in GitHub Desktop.
// Start by determining the build environment. The three main options are:
// - development (__DEV__): for development builds
// - test: (__TEST__): for running and developing tests
// - production: (__PROD__) for staging and production builds
//
// Additionally coverage and cli can be turned on changing the arguments passed in
const ENV = process.env.NODE_ENV || 'development'
const __PROD__ = ENV === 'production'
const webpack = require('webpack')
const makeDebug = require('debug')
const debug = makeDebug('app:webpack:config:browser')
const debugPlugins = makeDebug('app:webpack:config:browser:plugins')
const debugRules = makeDebug('app:webpack:config:browser:rules')
const path = require('path')
const CSS_MAPS = !__PROD__
const CSS_LOADER_OPTIONS = {
modules: false,
importLoaders: 0,
localIdentName: '[local]',
sourceMap: CSS_MAPS
}
const CSS_LOADER_MODULES_OPTIONS = Object.assign(
{},
CSS_LOADER_OPTIONS,
{
modules: true,
importLoaders: 1,
localIdentName: '[local]__[hash:base64:5]'
}
)
module.exports = function applyConfiguration (config) {
debug('Browser configuration')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
config.plugins.push(
// Bundle all styles into a file
// https://github.com/webpack-contrib/extract-text-webpack-plugin
new ExtractTextPlugin({
filename: 'style.css',
allChunks: __PROD__,
disable: !__PROD__
})
)
debugPlugins('Added extract text plugin')
// Transform our own .pcss .postcss .css files with PostCSS and CSS-modules
config.module.rules.push({
test: /\.((p(ost)?)?css)$/,
include: [
path.join(__dirname, '..', 'src')
],
loader: ExtractTextPlugin.extract({
fallback: {
loader: 'style-loader',
query: {
singleton: true
}
},
use: [
{
loader: 'css-loader',
query: CSS_LOADER_MODULES_OPTIONS
},
{
loader: 'postcss-loader'
}
]
})
})
debugRules('Transform our own postcss files with PostCSS and CSS-modules')
// Transform other modules .pcss .postcss .css files with PostCSS but without CSS-modules
config.module.rules.push({
test: /\.((p(ost)?)?css)$/,
exclude: [
path.join(__dirname, '..', 'src')
],
loader: ExtractTextPlugin.extract({
fallback: {
loader: 'style-loader',
query: {
singleton: true
}
},
use: [
{
loader: 'css-loader',
query: CSS_LOADER_OPTIONS
},
{
loader: 'postcss-loader'
}
]
})
})
debugRules('Transform other modules postcss files with PostCSS but without CSS-modules')
debug('Browser configuration done')
return config
}
// Start by determining the build environment. The three main options are:
// - development (__DEV__): for development builds
// - test: (__TEST__): for running and developing tests
// - production: (__PROD__) for staging and production builds
//
// Additionally coverage and cli can be turned on changing the arguments passed in
const ENV = process.env.NODE_ENV || 'development'
const __DEV__ = ENV === 'development'
const __PROD__ = ENV === 'production'
const __TEST__ = ENV === 'test'
const webpack = require('webpack')
const makeDebug = require('debug')
const debug = makeDebug('app:webpack:config:base')
const debugEnvironment = makeDebug('app:webpack:config:base:environment')
const debugPlugins = makeDebug('app:webpack:config:base:plugins')
const debugRules = makeDebug('app:webpack:config:base:rules')
const argv = require('yargs').argv
const path = require('path')
const dotenv = require('dotenv')
const __COVERAGE__ = !argv.watch && (argv.coverage || process.env.COVERAGE === 'on') && __TEST__
const __CLI__ = (argv.cli || process.env.CI === 'on')
debugEnvironment(`Environment ${ENV} with coverage=${__COVERAGE__} and CLI=${__CLI__}`)
if (!__DEV__ && !__PROD__ && !__TEST__) {
// Make sure the environment exists or the configuration will be in an undefined but buildable state
throw new Error(`${ENV} is not a valid environment`)
}
// Parse the argument and set options based on environment and arguments
const SERVICE_WORKER = process.env.SERVICE_WORKER || __PROD__
const HOT = argv.hot || false
const HOT_ONLY = argv.hotOnly || false
debugEnvironment(`This is HOT: ${HOT} ONLY: ${HOT_ONLY}`)
const JS_MAPS = !__PROD__
// Each key in the following object will be replaced in code with its value, as defined in this object. Try to load it from .env, unless its
// already passed in.
const env = Object.assign({
'FACEBOOK_APP_ID_DEV': process.env.FACEBOOK_APP_ID_DEV,
'FACEBOOK_APP_ID': process.env.FACEBOOK_APP_ID,
'PLAY_SERVICES_API_KEY_DEV': process.env.PLAY_SERVICES_API_KEY_DEV,
'PLAY_SERVICES_API_KEY': process.env.PLAY_SERVICES_API_KEY
}, dotenv.config({ silent: __CLI__ || __PROD__ }).parsed)
debugEnvironment(`Environment assigned ${Object.keys(env).length} variables`)
// These definitions will be passed into a plugin and each key will be replaced in webpack-processed source files by the value defined in
// the following object. It's an exact replacement, which means we need to put String into quotes so that when they are replaced, it's a
// string.
let DEFINITIONS = {
'process.env.NODE_ENV': JSON.stringify(ENV),
'process.env.SERVICE_WORKER': `'${SERVICE_WORKER ? 'on' : 'off'}'`,
'__DEV__': __DEV__,
'__PROD__': __PROD__,
'__TEST__': __TEST__,
'__COVERAGE__': __COVERAGE__,
'__CLI__': __CLI__
}
Object.keys(env).forEach((key) => {
const value = JSON.stringify(env[key])
DEFINITIONS[`process.env.${key}`] = value
const missing = value === undefined || value === '' || value === '""'
debugEnvironment(`${missing ? '☐' : '☑'} ${key} was ${missing ? 'not ' : ''}loaded. ${__DEV__ && !__CLI__ ? `[${value}]` : ''}`)
})
debugEnvironment(`Environment assigned ${Object.keys(DEFINITIONS).length} definitions`)
const plugins = []
const rules = []
const entry = {}
// Here the plugin definitions start. Each plugin has commentary on what it does, why it's added or is only loaded conditionally and a
// link to its source
plugins.push(
// Don't continue when there are error
new webpack.NoEmitOnErrorsPlugin(),
// Check for watch analyzerMode
// https://github.com/webpack/webpack/issues/
new (require('awesome-typescript-loader').CheckerPlugin)(),
// Resolve paths for Typescript 2+ baseUrl and paths.
// https://www.typescriptlang.org/docs/handbook/module-resolution.html
// https://github.com/s-panferov/awesome-typescript-loader#advanced-path-resolution-in-typescript-20
new (require('awesome-typescript-loader').TsConfigPathsPlugin)({
configFileName: 'tsconfig.json'
}),
// Compress all the files with the default settings, by preparing them with gzip compression source which may be delivered instead
// https://github.com/webpack-contrib/compression-webpack-plugin
new (require('compression-webpack-plugin'))(),
// Define the constants. Webpack will replace all keys in the object by the value in this object
// https://webpack.js.org/plugins/define-plugin/
new webpack.DefinePlugin(DEFINITIONS),
// Check for circular dependencies and break out if it fails.
// https://github.com/aackerman/circular-dependency-plugin
// Circular dependencies will make imports resolve to null, as the first time a file is refered, not all the exports will be available
// and thus resolving to null. This breaks the build unreliably. This plugin stops the build and shows an error.
new (require('circular-dependency-plugin'))({
exclude: /node_modules/,
failOnError: true
})
)
if (!__TEST__) {
// If the test environment is active, some of these plugins may not work as expected. Once this is no longer the case, they may be moved
// out of this conditional.
plugins.push(
// The chunks plugin bundles common code together into a chunk
// https://webpack.js.org/guides/code-splitting-libraries/#implicit-common-vendor-chunk
// https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318
//
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
// Code split common bundles
new webpack.optimize.CommonsChunkPlugin({
async: 'commonlazy.js',
children: true
}),
// Build the index.html with the bundl.js and optional styles.css
// https://github.com/jantimon/html-webpack-plugin
new (require('html-webpack-plugin'))({
template: './index.html',
minify: { collapseWhitespace: __PROD__ }
}),
// Copy assets over to the root of the distribution folder
// https://github.com/kevlened/copy-webpack-plugin
new (require('copy-webpack-plugin'))([
{ from: './favicon.ico', to : './' },
{ from: './manifest.json', to: './' }
])
)
debugPlugins('Added HTML webpack plugins and copied assets.')
}
if (JS_MAPS) {
// Load sourcemaps for all the included node_modules. These source maps are preloaded so that they may be used in the sourcemaps
// we create.
rules.push({
test: /\.jsx?$/,
enforce: 'pre',
exclude: [
path.resolve(__dirname, '..', 'src')
],
loader: 'source-map-loader'
})
debugRules('Added source-map-loader')
}
rules.push(
{
// Transform our js and jsx files using Babel
test: /\.jsx?$/,
exclude: /node_modules/,
loader: {
loader: 'babel-loader',
options: {
sourceMaps: JS_MAPS ? 1 : 0
}
}
},
{
// Transform our ts and tsx files using Typescript and Babel
test: /\.tsx?$/,
exclude: [
/node_modules/,
path.resolve(__dirname, '..', 'test')
],
loader: [
{
loader: 'awesome-typescript-loader',
options: {
babelOptions: {
sourceMaps: JS_MAPS ? 1 : 0
}
}
}
]
},
{
// Transform images using the image loader or fallback to url loader
test: /\.(jpe?g|png|gif|svg)$/i,
loader: [
{
loader: 'url-loader',
query: {
limit: 1024 * 1024 * 0.5
}
},
{
loader: 'img-loader',
query: {
progressive: true
}
}
]
},
{
// Deliver these files as is
test: /\.(xml|html?|text|md)$/i,
loader: 'raw-loader'
},
{
// Load fonts using the file loader in production
test: /\.(woff2?|ttf|eot)(\?.*)$/i,
loader: __PROD__ ? {
loader: 'file-loader',
options: {
name: '[path][name]_[hash:base64:5].[ext]'
}
} : {
loader: 'url-loader'
}
}
)
debugRules(`Added ${rules.length} rules`)
// Define the entry points for this app
// All the entries are serve up
entry['app'] = []
entry['app'].push('babel-polyfill')
debug('Added babel-polyfill as entry')
if (HOT) {
entry['app'].push('react-hot-loader/patch')
debug('Added react-hot-loader/patch as entry')
}
entry['app'].push('./styles/base.pcss')
debug('Added base normalize/reset css')
entry['app'].push('./index.ts')
debug('Added javascript entry')
module.exports =
{
context: path.resolve(__dirname, '..', 'src'),
// Defines the entries (these are added to the HTML),
entry,
// Build output definition
output: {
path: path.resolve(__dirname, '..', 'dist'),
publicPath: '/',
filename: `${HOT ? '' : '[chunkhash].'}[name].js`,
chunkFilename: `${HOT ? '' : '[chunkhash].'}[name].js`
},
// Resolve for webpack imports
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.json', '.pcss', '.css'],
// Base directories so you can import something inside src or node_moules without the need to type out src or node_modules.
// The order is important. This allows us to override node_modules in src. Finally also allow loading directly from the
// webpack root node_modules
modules: [
path.resolve(__dirname, '..', 'src'),
path.resolve(__dirname, '..', 'node_modules'),
'node_modules'
]
},
module: { rules },
plugins,
stats: {
children: false
},
node: {
global: true,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
setImmediate: false
},
externals: {
'react/addons': 'react'
},
devtool: 'source-map',
devServer: {
port: process.env.PORT || 3000,
host: 'localhost',
publicPath: '/',
contentBase: './src',
historyApiFallback: true,
open: true,
proxy: {
// OPTIONAL: proxy configuration:
// '/optional-prefix/**': { // path pattern to rewrite
// target: 'http://target-host.com',
// pathRewrite: path => path.replace(/^\/[^\/]+\//, '') // strip first path segment
// }
}
}
}
debug('Configuration ready')
const webpack = require('webpack')
const config = require('./base.babel')
const makeDebug = require('debug')
const debug = makeDebug('app:webpack:config:development')
const debugPlugins = makeDebug('app:webpack:config:development:plugins')
debug('Development configuration')
config.plugins.unshift(new webpack.NamedModulesPlugin())
debugPlugins('Added the named modules plugin')
module.exports = require('./_browser.babel')(config)
debug('Development configuration ready')
const path = require('path')
const makeDebug = require('debug')
const debug = makeDebug('app:webpack:config')
// Start by determining the build environment. The three main options are:
// - development (__DEV__): for development builds
// - test: (__TEST__): for running and developing tests
// - production: (__PROD__) for staging and production builds
//
// Additionally coverage and cli can be turned on changing the arguments passed in
const ENV = process.env.NODE_ENV || 'development'
const __DEV__ = ENV === 'development'
const __PROD__ = ENV === 'production'
const __TEST__ = ENV === 'test'
function config () {
if (__PROD__) {
return 'production'
}
if (__TEST__) {
return 'test'
}
if (__DEV__) {
return 'development'
}
throw new Error(`${ENV} is not a valid environment`)
}
debug('Loading webpack configuration')
debug('Environment is %s', ENV)
const configuration = config()
const resolvedPath = path.resolve(__dirname, 'webpack', `${configuration}.babel.js`)
debug('Configuration is %s', configuration)
debug('Loading from %s', resolvedPath)
module.exports = require(resolvedPath)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment