import { DefinePlugin, NamedModulesPlugin, HashedModuleIdsPlugin, LoaderOptionsPlugin, ContextReplacementPlugin, HotModuleReplacementPlugin, NoEmitOnErrorsPlugin, optimize, } from 'webpack'; import path from 'path'; import { isatty } from 'tty'; import chalk from 'chalk'; import _debug from 'debug'; import slash from 'slash'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import ExtractTextPlugin from 'extract-text-webpack-plugin'; import HtmlPlugin from 'html-webpack-plugin'; import ProgressBarPlugin from 'progress-bar-webpack-plugin'; import Environment from '../../config/webpack/environment'; import injectManifestPlugin from '../../config/webpack/injectManifestPlugin'; const debug = _debug('webpack:config'); debug.generated = _debug('webpack:config:generated'); debug.generated('filename:', __filename); debug.generated('dirname:', __dirname); const { CommonsChunkPlugin, UglifyJsPlugin, ModuleConcatenationPlugin, } = optimize; const websiteSubfolder = 'website'; export default (options) => { const environment = new Environment({ ExtractTextPlugin, // @HACK }); environment.input(options); debug.generated(environment); const { development, production, server, locales, autoprefixerTargets, } = environment; const baseOutputFilename = development ? 'assets/dev/[name].dev.[ext]' // Always use a hash (in production) to prevent files with the same name causing issues : 'assets/prod/[chunkhash:2]/[name].[chunkhash:8].[ext]'; const webpackChunkFilename = baseOutputFilename .replace(/\[ext(.*?)\]/g, 'js'); const webpackOutputFilename = webpackChunkFilename; const assetOutputFilename = baseOutputFilename .replace(/\[chunkhash(.*?)\]/g, '[hash$1]'); const cssOutputFileName = baseOutputFilename .replace(/\[ext(.*?)\]/g, 'css') .replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]'); const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`; // Default options for url-loader const urlLoaderOptions = { limit: 1, // Don't inline anything (but empty files) by default name: assetOutputFilename, publicPath: cssOutputRebasePath, }; const config = { name: 'frontend', target: 'web', devServer: { // inline: true, headers: { 'Access-Control-Allow-Origin': '*', }, historyApiFallback: true, hot: true, noInfo: true, overlay: true, publicPath: '', quiet: false, watchOptions: { ignored: /node_modules/, }, }, module: { rules: [ { test: /\.jsx?/, loader: 'babel-loader', exclude: /node_modules/, options: { // Look for babel configuration in project directory babelrc: false, // Cache transformations to the filesystem (in default temp dir) cacheDirectory: true, presets: [ ['babel-preset-env', { targets: { browsers: autoprefixerTargets, uglify: false, }, // spec: true, // debug: development, modules: false, // do not transpile modules, webpack 2+ does that }], 'babel-preset-react', ], plugins: [ 'babel-plugin-transform-react-constant-elements', 'babel-plugin-transform-class-properties', ['babel-plugin-transform-runtime', { helpers: false, polyfill: false, regenerator: true, }], 'babel-plugin-dynamic-import-webpack', ], }, }, ...[ /\.(gif|png|webp)$/i, // graphics /\.svg$/i, // svg /\.jpe?g$/i, // jpeg /\.(mp4|ogg|webm)$/i, // video /\.(eot|otf|ttf|woff|woff2)$/i, // fonts /\.(wav|mp3|m4a|aac|oga)$/i, // audio ].map(test => ({ test, loader: 'url-loader', options: urlLoaderOptions, })), { test: /\.css$/, use: environment.styleLoaders(), }, { test: /\.s[ac]ss$/, use: environment.styleLoaders( { loader: 'sass-loader', }, ), }, ], strictExportPresence: true, }, output: { filename: webpackOutputFilename, chunkFilename: webpackChunkFilename, path: path.join(__dirname, 'lib', websiteSubfolder), publicPath: '', }, plugins: [ // Show progress as a bar during build isatty(1) && new ProgressBarPlugin({ complete: chalk.white('\u2588'), incomplete: chalk.grey('\u2591'), format: `:bar ${chalk.cyan.bold(':percent')} Webpack build: ${chalk.grey(':msg')}`, }), // Enforce case-sensitive import paths new CaseSensitivePathsPlugin(), // Replace specified expressions with values new DefinePlugin({ 'process.env.__DEV__': JSON.stringify(development), 'process.env.__PROD__': JSON.stringify(production), 'process.env.__SERVER__': JSON.stringify(server), 'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development'), }), // Dev server build ...[ // Hot module reloading new HotModuleReplacementPlugin(), new NoEmitOnErrorsPlugin(), // Use paths as names when serving new NamedModulesPlugin(), ].filter(() => server), // If we're not serving, we're creating a static build ...[// Extract imported stylesheets out into .css files new ExtractTextPlugin({ allChunks: true, filename: cssOutputFileName, }), ].filter(() => !server), // Move modules imported from node_modules/ into a vendor chunk when enabled new CommonsChunkPlugin({ name: 'vendor', minChunks(module) { return (module.resource && module.resource.includes('node_modules')); }, }), // If we're generating an HTML file, we must be building a web app, so // configure deterministic hashing for long-term caching. // Generate stable module ids instead of having Webpack assign integers. // NamedModulesPlugin allows for easier debugging and // HashedModuleIdsPlugin does this without adding too much to bundle // size. development ? new NamedModulesPlugin() : new HashedModuleIdsPlugin(), // The Webpack manifest is normally folded into the last chunk, changing // its hash - prevent this by extracting the manifest into its own // chunk - also essential for deterministic hashing. new CommonsChunkPlugin({ name: 'manifest' }), // Inject the Webpack manifest into the generated HTML as a