UNPKG

@mapbox/batfish

Version:

The React-powered static-site generator you didn't know you wanted

175 lines (165 loc) 6.26 kB
// 'use strict'; const _ = require('lodash'); const path = require('path'); const webpack = require('webpack'); const crypto = require('crypto'); const webpackMerge = require('webpack-merge'); const AssetsPlugin = require('assets-webpack-plugin'); const UglifyPlugin = require('uglifyjs-webpack-plugin'); const resolveFrom = require('resolve-from'); const createWebpackConfigBase = require('./create-webpack-config-base'); const constants = require('./constants'); // We need the directory for the module instead of the filename to its main // file. function resolveModuleDirectoryFrom(src , name ) { return resolveFrom(src, name).replace( /node_modules\/([^/]+).*$/, 'node_modules/$1' ); } // Create a Webpack configuration for all the assets that will be loaded by the client. function createWebpackConfigClient( batfishConfig , options ) { // Resolve these peerDependencies from the pagesDirectory so we are sure // to get the same version that the pages are getting. Alias them below. const reactPath = resolveModuleDirectoryFrom( batfishConfig.pagesDirectory, 'react' ); const reactDomPath = resolveModuleDirectoryFrom( batfishConfig.pagesDirectory, 'react-dom' ); const reactHelmetPath = resolveModuleDirectoryFrom( batfishConfig.pagesDirectory, 'react-helmet' ); return createWebpackConfigBase(batfishConfig, { target: constants.TARGET_BROWSER }).then(baseConfig => { let vendorModules = [ reactPath, reactDomPath, reactHelmetPath, require.resolve('@mapbox/scroll-restorer'), require.resolve('@mapbox/link-hijacker') ]; if (batfishConfig.includePromisePolyfill) { vendorModules.unshift(require.resolve('es6-promise/auto')); } if (batfishConfig.vendorModules !== undefined) { vendorModules = vendorModules.concat(batfishConfig.vendorModules); } const clientPlugins = [ // Emit a file with assets' paths. // This is used in build processes to grab built files, whose names // include hashes so cannot be known without this dictionary. new AssetsPlugin({ path: path.resolve(batfishConfig.outputDirectory), filename: 'assets.json', processOutput: x => JSON.stringify(x, null, 2) }), // Extract universal vendor files (defined above) from everything else. new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: Infinity }), // Bundle together any other modules from anywhere imported more than 3 times. new webpack.optimize.CommonsChunkPlugin({ name: 'app', children: true, minChunks: 4 }), // Trying to follow advice for long-term caching described here: // https://webpack.js.org/guides/caching/#extracting-boilerplate and // https://jeremygayed.com/dynamic-vendor-bundling-in-webpack-528993e48aab#.hjgai17ap // Because 'manifest' does not correspond to an entry name, this chunk // will include Webpack's runtime boilerplate and manifest, which can // change with each build. During the static build we inject it directly // into the HTML, so those variations do not ruin caching on large chunks. new webpack.optimize.CommonsChunkPlugin('manifest'), // Recommended at https://webpack.js.org/guides/caching/#module-identifiers // as a way to make module IDs more deterministic. new webpack.HashedModuleIdsPlugin(), new webpack.NamedChunksPlugin(chunk => { if (chunk.name) return chunk.name; const hashSeed = chunk.modules .map(m => m.resource || '') .sort() .map(resource => { return path.relative(batfishConfig.pagesDirectory, resource); }) .join('_'); const chunkHash = crypto .createHash('md5') .update(hashSeed) .digest('hex'); return chunkHash; }), // Define an environment variable for special cases new webpack.DefinePlugin({ 'process.env.DEV_SERVER': (options && options.devServer) || false }) ].concat(batfishConfig.webpackPlugins || []); if (batfishConfig.production) { const uglifyPlugin = new UglifyPlugin({ sourceMap: true, cache: true, parallel: true, uglifyOptions: { compress: { // Disabled because of an issue with Uglify breaking seemingly valid code: // https://github.com/facebookincubator/create-react-app/issues/2376 // Pending further investigation: // https://github.com/mishoo/UglifyJS2/issues/2011 comparisons: false }, output: { // Turned on because emoji and regex is not minified properly using default // https://github.com/facebookincubator/create-react-app/issues/2488 ascii_only: true } } }); clientPlugins.push(uglifyPlugin); } const appEntry = []; if (!batfishConfig.production && batfishConfig.inlineJs) { batfishConfig.inlineJs.forEach(jsData => { appEntry.push(jsData.filename); }); } appEntry.push(path.join(__dirname, '../webpack/batfish-app.js')); const clientConfig = { entry: { app: appEntry, vendor: vendorModules }, output: { filename: !batfishConfig.production ? '[name].js' : '[name]-[chunkhash].js', chunkFilename: !batfishConfig.production ? '[name].chunk.js' : '[name]-[chunkhash].chunk.js' }, resolve: { alias: Object.assign({}, _.get(baseConfig, 'resolve.alias'), { react: reactPath, 'react-dom': reactDomPath, 'react-helmet': reactHelmetPath }) }, target: 'web', plugins: clientPlugins }; let config = webpackMerge(baseConfig, clientConfig); if (batfishConfig.webpackConfigClientTransform) { config = batfishConfig.webpackConfigClientTransform(config); } return config; }); } module.exports = createWebpackConfigClient;