webpack.config.js 8.9 KB
Newer Older
1 2 3
const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
4
const VueLoaderPlugin = require('vue-loader/lib/plugin');
5 6
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const CompressionPlugin = require('compression-webpack-plugin');
7
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
P
Phil Hughes 已提交
8
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
9 10

const ROOT_PATH = path.resolve(__dirname, '..');
11
const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache');
12
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
P
Phil Hughes 已提交
13
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
14 15
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
16
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
17 18
const WEBPACK_REPORT = process.env.WEBPACK_REPORT;
const NO_COMPRESSION = process.env.NO_COMPRESSION;
19
const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS;
20

21 22 23
const VUE_VERSION = require('vue/package.json').version;
const VUE_LOADER_VERSION = require('vue-loader/package.json').version;

24 25
const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';

26 27
let autoEntriesCount = 0;
let watchAutoEntries = [];
28
const defaultEntries = ['./main'];
29 30 31

function generateEntries() {
  // generate automatic entry points
32
  const autoEntries = {};
33
  const autoEntriesMap = {};
M
Mike Greiling 已提交
34 35 36 37
  const pageEntries = glob.sync('pages/**/index.js', {
    cwd: path.join(ROOT_PATH, 'app/assets/javascripts'),
  });
  watchAutoEntries = [path.join(ROOT_PATH, 'app/assets/javascripts/pages/')];
38 39 40

  function generateAutoEntries(path, prefix = '.') {
    const chunkPath = path.replace(/\/index\.js$/, '');
41
    const chunkName = chunkPath.replace(/\//g, '.');
42
    autoEntriesMap[chunkName] = `${prefix}/${path}`;
43
  }
44

M
Mike Greiling 已提交
45
  pageEntries.forEach(path => generateAutoEntries(path));
46

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
  const autoEntryKeys = Object.keys(autoEntriesMap);
  autoEntriesCount = autoEntryKeys.length;

  // import ancestor entrypoints within their children
  autoEntryKeys.forEach(entry => {
    const entryPaths = [autoEntriesMap[entry]];
    const segments = entry.split('.');
    while (segments.pop()) {
      const ancestor = segments.join('.');
      if (autoEntryKeys.includes(ancestor)) {
        entryPaths.unshift(autoEntriesMap[ancestor]);
      }
    }
    autoEntries[entry] = defaultEntries.concat(entryPaths);
  });
62

63
  const manualEntries = {
64
    default: defaultEntries,
M
Mike Greiling 已提交
65
    raven: './raven/index.js',
66 67 68 69 70
  };

  return Object.assign(manualEntries, autoEntries);
}

71
module.exports = {
M
Mike Greiling 已提交
72 73
  mode: IS_PRODUCTION ? 'production' : 'development',

74 75 76
  context: path.join(ROOT_PATH, 'app/assets/javascripts'),

  entry: generateEntries,
77 78 79 80

  output: {
    path: path.join(ROOT_PATH, 'public/assets/webpack'),
    publicPath: '/assets/webpack/',
81 82
    filename: IS_PRODUCTION ? '[name].[chunkhash:8].bundle.js' : '[name].bundle.js',
    chunkFilename: IS_PRODUCTION ? '[name].[chunkhash:8].chunk.js' : '[name].chunk.js',
83
    globalObject: 'this', // allow HMR and web workers to play nice
84 85
  },

86 87 88 89 90 91 92 93 94 95 96
  resolve: {
    extensions: ['.js'],
    alias: {
      '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
      emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
      empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
      icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
      images: path.join(ROOT_PATH, 'app/assets/images'),
      vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
      vue$: 'vue/dist/vue.esm.js',
      spec: path.join(ROOT_PATH, 'spec/javascripts'),
M
Mike Greiling 已提交
97 98 99
    },
  },

M
Mike Greiling 已提交
100
  module: {
101
    strictExportPresence: true,
M
Mike Greiling 已提交
102
    rules: [
M
Mike Greiling 已提交
103
      {
104
        test: /\.js$/,
105
        exclude: path => /node_modules|vendor[\\/]assets/.test(path) && !/\.vue\.js/.test(path),
M
Mike Greiling 已提交
106
        loader: 'babel-loader',
107
        options: {
108
          cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
109
        },
F
Filipa Lacerda 已提交
110
      },
111 112
      {
        test: /\.vue$/,
M
Mike Greiling 已提交
113
        loader: 'vue-loader',
114 115 116 117 118 119 120 121 122
        options: {
          cacheDirectory: path.join(CACHE_PATH, 'vue-loader'),
          cacheIdentifier: [
            process.env.NODE_ENV || 'development',
            webpack.version,
            VUE_VERSION,
            VUE_LOADER_VERSION,
          ].join('|'),
        },
123
      },
F
Filipa Lacerda 已提交
124 125
      {
        test: /\.svg$/,
M
Mike Greiling 已提交
126 127
        loader: 'raw-loader',
      },
S
Sam Rose 已提交
128
      {
129
        test: /\.(gif|png)$/,
S
Sam Rose 已提交
130
        loader: 'url-loader',
131
        options: { limit: 2048 },
S
Sam Rose 已提交
132
      },
P
Phil Hughes 已提交
133 134
      {
        test: /\_worker\.js$/,
135 136 137 138
        use: [
          {
            loader: 'worker-loader',
            options: {
139
              name: '[name].[hash:8].worker.js',
140 141 142 143
            },
          },
          'babel-loader',
        ],
P
Phil Hughes 已提交
144
      },
M
Mike Greiling 已提交
145
      {
146
        test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
S
Sam Rose 已提交
147 148
        exclude: /node_modules/,
        loader: 'file-loader',
149
        options: {
150
          name: '[name].[hash:8].[ext]',
M
Mike Greiling 已提交
151
        },
S
Sam Rose 已提交
152
      },
153
      {
154
        test: /.css$/,
155
        use: [
156
          'vue-style-loader',
157
          {
158 159
            loader: 'css-loader',
            options: {
160
              name: '[name].[hash:8].[ext]',
M
Mike Greiling 已提交
161
            },
162 163 164 165 166 167 168 169
          },
        ],
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        include: /node_modules\/katex\/dist\/fonts/,
        loader: 'file-loader',
        options: {
170
          name: '[name].[hash:8].[ext]',
M
Mike Greiling 已提交
171
        },
172
      },
173
    ],
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
  },

  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      maxInitialRequests: 4,
      cacheGroups: {
        default: false,
        common: () => ({
          priority: 20,
          name: 'main',
          chunks: 'initial',
          minChunks: autoEntriesCount * 0.9,
        }),
        vendors: {
          priority: 10,
          chunks: 'async',
          test: /[\\/](node_modules|vendor[\\/]assets[\\/]javascripts)[\\/]/,
        },
        commons: {
          chunks: 'all',
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
    },
M
Mike Greiling 已提交
200 201
  },

202 203 204
  plugins: [
    // manifest filename must match config.webpack.manifest_filename
    // webpack-rails only needs assetsByChunkName to function properly
205 206 207
    new StatsWriterPlugin({
      filename: 'manifest.json',
      transform: function(data, opts) {
208
        const stats = opts.compiler.getStats().toJson({
209 210 211 212
          chunkModules: false,
          source: false,
          chunks: false,
          modules: false,
M
Mike Greiling 已提交
213
          assets: true,
214 215
        });
        return JSON.stringify(stats, null, 2);
M
Mike Greiling 已提交
216
      },
P
Phil Hughes 已提交
217
    }),
M
Mike Greiling 已提交
218

219 220 221
    // enable vue-loader to use existing loader rules for other module types
    new VueLoaderPlugin(),

222 223 224
    // automatically configure monaco editor web workers
    new MonacoWebpackPlugin(),

M
Mike Greiling 已提交
225
    // prevent pikaday from including moment.js
P
Phil Hughes 已提交
226
    new webpack.IgnorePlugin(/moment/, /pikaday/),
M
Mike Greiling 已提交
227

228 229 230 231 232 233
    // fix legacy jQuery plugins which depend on globals
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),

234 235
    // compression can require a lot of compute time and is disabled in CI
    IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(),
236

237 238 239 240 241 242 243 244 245 246
    // WatchForChangesPlugin
    // TODO: publish this as a separate plugin
    IS_DEV_SERVER && {
      apply(compiler) {
        compiler.hooks.emit.tapAsync('WatchForChangesPlugin', (compilation, callback) => {
          const missingDeps = Array.from(compilation.missingDependencies);
          const nodeModulesPath = path.join(ROOT_PATH, 'node_modules');
          const hasMissingNodeModules = missingDeps.some(
            file => file.indexOf(nodeModulesPath) !== -1
          );
247

248 249 250 251 252
          // watch for changes to missing node_modules
          if (hasMissingNodeModules) compilation.contextDependencies.add(nodeModulesPath);

          // watch for changes to automatic entrypoints
          watchAutoEntries.forEach(watchPath => compilation.contextDependencies.add(watchPath));
253

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
          // report our auto-generated bundle count
          console.log(
            `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`
          );

          callback();
        });
      },
    },

    // enable HMR only in webpack-dev-server
    DEV_SERVER_LIVERELOAD && new webpack.HotModuleReplacementPlugin(),

    // optionally generate webpack bundle analysis
    WEBPACK_REPORT &&
      new BundleAnalyzerPlugin({
        analyzerMode: 'static',
        generateStatsFile: true,
        openAnalyzer: false,
        reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'),
        statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'),
      }),
  ].filter(Boolean),

  devServer: {
279
    host: DEV_SERVER_HOST,
280
    port: DEV_SERVER_PORT,
281
    disableHostCheck: true,
282 283 284 285
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': '*',
    },
286
    stats: 'errors-only',
S
Simon Knox 已提交
287
    hot: DEV_SERVER_LIVERELOAD,
M
Mike Greiling 已提交
288
    inline: DEV_SERVER_LIVERELOAD,
289
  },
290

291
  devtool: NO_SOURCEMAPS ? false : devtool,
292

293 294 295
  // sqljs requires fs
  node: { fs: 'empty' },
};