webpack-config-spaceship
Version:
Webpack config to get a project off the ground fast.
223 lines (210 loc) • 6.45 kB
JavaScript
const path = require('path');
const pathExists = require('path-exists');
const fs = require('fs');
const findUp = require('find-up');
const merge = require('lodash.merge');
const autoprefixer = require('autoprefixer');
const findBabelConfig = require('find-babel-config');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const spaceshipBabelConfig = require('babel-preset-spaceship');
const { ENV } = require('./constants');
const paths = require('./paths');
const { defaultFilter, identity, getAssetsFromStats } = require('./utils');
function getWebpackConfig(opts = {}) {
if (!opts.entry) {
throw new Error('Required option "entry" is missing.');
}
const options = merge({
module: {},
output: {},
}, opts);
const cwd = options.cwd || process.cwd();
const contextPath = options.context || paths.contextPath;
const outputPath = options.output.path || paths.outputPath;
const publicPath = options.output.publicPath || `${process.env.HOST}/static`;
const statsPath = path.join(cwd, options.statsPath || path.join(contextPath, './webpack.stats.json'));
const manifestPath = path.join(cwd, options.manifestPath || path.join(contextPath, './webpack.manifest.json'));
const pkgPath = findUp.sync('package.json', { cwd });
const pkg = require(pkgPath); // eslint-disable-line global-require, import/no-dynamic-require
const babelConfig = merge(
{ plugins: ['react-hot-loader/babel'] },
spaceshipBabelConfig,
findBabelConfig.sync(cwd).config
);
const rules = [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: Object.assign(
{
babelrc: false,
cacheDirectory: true,
forceEnv: 'webpack',
},
babelConfig
),
},
],
},
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.html$/, loader: 'html-loader' },
{
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.(css|scss)$/,
/\.json$/,
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.svg$/,
],
loader: 'file-loader',
},
{
test: [
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.svg$/,
],
loader: 'url-loader',
options: {
limit: 10000,
},
},
{
test: /\.(css|scss)$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
minimize: ENV !== 'development',
importLoaders: 1,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
minimize: ENV !== 'development',
includePaths: [
`${cwd}/${contextPath}`,
`${cwd}/node_modules`,
],
},
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
plugins() {
return [
autoprefixer({
browsers: [
'last 2 versions',
'Firefox ESR',
],
}),
];
},
},
},
],
}),
},
];
const entry = typeof options.entry === 'string' ? { main: options.entry } : options.entry;
if (options.vendor !== false) {
let vendor = ['babel-polyfill', 'whatwg-fetch'];
if (Array.isArray(entry.vendor)) {
vendor = entry.vendor.concat(vendor);
} else if (typeof entry.vendor === 'string') {
vendor.unshift(entry.vendor);
} else if (pathExists.sync(`${cwd}/${contextPath}/vendor.js`)) {
vendor.unshift('./vendor.js');
} else if (pathExists.sync(`${cwd}/${contextPath}/vendor.jsx`)) {
vendor.unshift('./vendor.jsx');
}
entry.vendor = vendor;
}
const chunks = Object.keys(entry);
let plugins = [
new webpack.optimize.OccurrenceOrderPlugin(true),
(ENV === 'development') && new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.EnvironmentPlugin({
NODE_ENV: ENV,
VERSION: pkg.version,
}),
(options.stylesheets !== false) && new ExtractTextPlugin('[name].[hash].css'),
new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]),
(options.common !== false) && new webpack.optimize.CommonsChunkPlugin({
name: 'common',
chunks: chunks.filter(options.filterCommonChunks || defaultFilter),
}),
(ENV === 'production') && new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
sourceMap: true,
}),
function saveWebpackStats() {
this.plugin('done', webpackStats => {
const stats = webpackStats.toJson();
fs.writeFileSync(
statsPath,
JSON.stringify(stats, null, 2)
);
const manifest = getAssetsFromStats(stats);
fs.writeFileSync(
manifestPath,
JSON.stringify(manifest, null, 2)
);
});
},
];
plugins = plugins.filter(plugin => Boolean(plugin));
const filterRule = options.filterRule || defaultFilter;
const mapRule = options.mapRule || identity;
const filterPlugin = options.filterPlugin || defaultFilter;
const mapPlugin = options.mapPlugin || identity;
return merge({}, {
entry,
output: {
filename: '[name].[hash].js',
sourceMapFilename: '[file].map',
path: path.join(cwd, outputPath),
pathinfo: true,
publicPath,
},
module: {
rules: rules.filter(filterRule).map(mapRule),
},
plugins: plugins.filter(filterPlugin).map(mapPlugin),
resolve: {
extensions: ['.js', '.json', '.jsx'],
modules: [
'node_modules',
path.join(cwd, contextPath),
],
},
context: path.join(cwd, contextPath),
devServer: {
hot: true,
contentBase: path.join(cwd, outputPath),
publicPath,
},
devtool: (ENV === 'production' ? 'source-map' : 'cheap-module-source-map'),
}, options.merge || {});
}
module.exports = getWebpackConfig;