webpack.config.js 11.1 KB
Newer Older
1 2
'use strict';

3
var crypto = require('crypto');
P
Phil Hughes 已提交
4
var fs = require('fs');
5
var path = require('path');
6
var glob = require('glob');
7
var webpack = require('webpack');
8
var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
9
var CopyWebpackPlugin = require('copy-webpack-plugin');
M
Mike Greiling 已提交
10
var CompressionPlugin = require('compression-webpack-plugin');
11
var NameAllModulesPlugin = require('name-all-modules-plugin');
12
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
13
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
14

15
var ROOT_PATH = path.resolve(__dirname, '..');
16
var IS_PRODUCTION = process.env.NODE_ENV === 'production';
17
var IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
18
var DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
19
var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
20
var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
21
var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
M
Mike Greiling 已提交
22
var NO_COMPRESSION = process.env.NO_COMPRESSION;
23

24 25 26 27
// generate automatic entry points
var autoEntries = {};
var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });

28 29
function generateAutoEntries(path, prefix = '.') {
  const chunkPath = path.replace(/\/index\.js$/, '');
30 31
  const chunkName = chunkPath.replace(/\//g, '.');
  autoEntries[chunkName] = `${prefix}/${path}`;
32 33 34
}

pageEntries.forEach(( path ) => generateAutoEntries(path));
35

36 37 38 39
// report our auto-generated bundle count
var autoEntriesCount = Object.keys(autoEntries).length;
console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`);

40
var config = {
41 42 43 44
  // because sqljs requires fs.
  node: {
    fs: "empty"
  },
45
  context: path.join(ROOT_PATH, 'app/assets/javascripts'),
46
  entry: {
47
    balsamiq_viewer:      './blob/balsamiq_viewer.js',
48
    common:               './commons/index.js',
49
    common_vue:           './vue_shared/vue_resource_interceptor.js',
50 51
    cycle_analytics:      './cycle_analytics/cycle_analytics_bundle.js',
    environments:         './environments/environments_bundle.js',
52
    filtered_search:      './filtered_search/filtered_search_bundle.js',
53
    help:                 './help/help.js',
54
    merge_conflicts:      './merge_conflicts/merge_conflicts_bundle.js',
55
    monitoring:           './monitoring/monitoring_bundle.js',
P
Phil Hughes 已提交
56
    notebook_viewer:      './blob/notebook_viewer.js',
S
Sam Rose 已提交
57
    pdf_viewer:           './blob/pdf_viewer.js',
58
    pipelines_details:    './pipelines/pipeline_details_bundle.js',
59
    profile:              './profile/profile_bundle.js',
60
    project_import_gl:    './projects/project_import_gitlab_project.js',
61
    protected_branches:   './protected_branches',
62
    protected_tags:       './protected_tags',
63
    registry_list:        './registry/index.js',
64
    sidebar:              './sidebar/sidebar_bundle.js',
65
    snippet:              './snippet/snippet_bundle.js',
66
    sketch_viewer:        './blob/sketch_viewer.js',
P
Phil Hughes 已提交
67
    stl_viewer:           './blob/stl_viewer.js',
68
    terminal:             './terminal/terminal_bundle.js',
69 70
    ui_development_kit:   './ui_development_kit.js',
    two_factor_auth:      './two_factor_auth.js',
71 72 73 74 75 76 77 78 79 80


    common:               './commons/index.js',
    common_vue:           './vue_shared/vue_resource_interceptor.js',
    locale:               './locale/index.js',
    main:                 './main.js',
    ide:                  './ide/index.js',
    raven:                './raven/index.js',
    test:                 './test.js',
    u2f:                  ['vendor/u2f'],
81
    webpack_runtime:      './webpack.js',
82 83 84 85 86
  },

  output: {
    path: path.join(ROOT_PATH, 'public/assets/webpack'),
    publicPath: '/assets/webpack/',
87 88
    filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js',
    chunkFilename: IS_PRODUCTION ? '[name].[chunkhash].chunk.js' : '[name].chunk.js',
89 90
  },

M
Mike Greiling 已提交
91
  module: {
M
Mike Greiling 已提交
92
    rules: [
M
Mike Greiling 已提交
93
      {
94
        test: /\.js$/,
95
        exclude: /(node_modules|vendor\/assets)/,
M
Mike Greiling 已提交
96
        loader: 'babel-loader',
F
Filipa Lacerda 已提交
97
      },
98 99
      {
        test: /\.vue$/,
M
Mike Greiling 已提交
100
        loader: 'vue-loader',
101
      },
F
Filipa Lacerda 已提交
102 103
      {
        test: /\.svg$/,
M
Mike Greiling 已提交
104 105
        loader: 'raw-loader',
      },
S
Sam Rose 已提交
106
      {
107
        test: /\.(gif|png)$/,
S
Sam Rose 已提交
108
        loader: 'url-loader',
109
        options: { limit: 2048 },
S
Sam Rose 已提交
110
      },
P
Phil Hughes 已提交
111 112
      {
        test: /\_worker\.js$/,
P
Phil Hughes 已提交
113
        use: [
P
Phil Hughes 已提交
114
          {
T
Tim Zallmann 已提交
115
            loader: 'worker-loader',
P
Phil Hughes 已提交
116
            options: {
T
Tim Zallmann 已提交
117 118 119
              inline: true
            }
          },
P
Phil Hughes 已提交
120 121
          { loader: 'babel-loader' },
        ],
P
Phil Hughes 已提交
122
      },
M
Mike Greiling 已提交
123
      {
124
        test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
S
Sam Rose 已提交
125 126
        exclude: /node_modules/,
        loader: 'file-loader',
127 128 129
        options: {
          name: '[name].[hash].[ext]',
        }
S
Sam Rose 已提交
130
      },
131 132 133 134 135
      {
        test: /katex.css$/,
        include: /node_modules\/katex\/dist/,
        use: [
          { loader: 'style-loader' },
136
          {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
            loader: 'css-loader',
            options: {
              name: '[name].[hash].[ext]'
            }
          },
        ],
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        include: /node_modules\/katex\/dist\/fonts/,
        loader: 'file-loader',
        options: {
          name: '[name].[hash].[ext]',
        }
      },
152 153 154 155 156 157 158 159 160 161
      {
        test: /monaco-editor\/\w+\/vs\/loader\.js$/,
        use: [
          { loader: 'exports-loader', options: 'l.global' },
          { loader: 'imports-loader', options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined' },
        ],
      }
    ],

    noParse: [/monaco-editor\/\w+\/vs\//],
162
    strictExportPresence: true,
M
Mike Greiling 已提交
163 164
  },

165 166 167
  plugins: [
    // manifest filename must match config.webpack.manifest_filename
    // webpack-rails only needs assetsByChunkName to function properly
168 169 170 171 172 173 174 175 176 177 178 179
    new StatsWriterPlugin({
      filename: 'manifest.json',
      transform: function(data, opts) {
        var stats = opts.compiler.getStats().toJson({
          chunkModules: false,
          source: false,
          chunks: false,
          modules: false,
          assets: true
        });
        return JSON.stringify(stats, null, 2);
      }
P
Phil Hughes 已提交
180
    }),
M
Mike Greiling 已提交
181 182

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

185 186 187 188 189 190
    // fix legacy jQuery plugins which depend on globals
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),

191
    // assign deterministic module ids
192
    new webpack.NamedModulesPlugin(),
193
    new NameAllModulesPlugin(),
194

195 196 197 198 199
    // assign deterministic chunk ids
    new webpack.NamedChunksPlugin((chunk) => {
      if (chunk.name) {
        return chunk.name;
      }
200 201 202 203 204 205 206 207 208 209

      const moduleNames = [];

      function collectModuleNames(m) {
        // handle ConcatenatedModule which does not have resource nor context set
        if (m.modules) {
          m.modules.forEach(collectModuleNames);
          return;
        }

210
        const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
211

212
        if (m.resource.indexOf(pagesBase) === 0) {
213
          moduleNames.push(path.relative(pagesBase, m.resource)
214
            .replace(/\/index\.[a-z]+$/, '')
215 216 217
            .replace(/\//g, '__'));
        } else {
          moduleNames.push(path.relative(m.context, m.resource));
218
        }
219 220 221 222 223 224 225 226 227
      }

      chunk.forEachModule(collectModuleNames);

      const hash = crypto.createHash('sha256')
        .update(moduleNames.join('_'))
        .digest('hex');

      return `${moduleNames[0]}-${hash.substr(0, 6)}`;
228 229
    }),

230 231 232 233 234 235
    // create cacheable common library bundle for all vue chunks
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common_vue',
      chunks: [
        'boards',
        'cycle_analytics',
P
Phil Hughes 已提交
236
        'deploy_keys',
237
        'environments',
238
        'filtered_search',
A
Alfredo Sumaran 已提交
239
        'groups',
240
        'merge_conflicts',
241
        'monitoring',
P
Phil Hughes 已提交
242
        'notebook_viewer',
S
Sam Rose 已提交
243
        'pdf_viewer',
244
        'pipelines',
245
        'pipelines_details',
246
        'registry_list',
247
        'ide',
248 249 250
        'schedule_form',
        'schedules_index',
        'sidebar',
251
        'vue_merge_request_widget',
252
      ],
253 254 255
      minChunks: function(module, count) {
        return module.resource && (/vue_shared/).test(module.resource);
      },
256 257
    }),

258
    // create cacheable common library bundles
259
    new webpack.optimize.CommonsChunkPlugin({
260
      names: ['main', 'common', 'webpack_runtime'],
261
    }),
262

M
Mike Greiling 已提交
263 264 265
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),

266
    // copy pre-compiled vendor libraries verbatim
267 268
    new CopyWebpackPlugin([
      {
269 270 271
        from: path.join(ROOT_PATH, `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`),
        to: 'monaco-editor/vs',
        transform: function(content, path) {
272
          if (/\.js$/.test(path) && !/worker/i.test(path) && !/typescript/i.test(path)) {
273 274 275
            return (
              '(function(){\n' +
              'var define = this.define, require = this.require;\n' +
276
              'window.define = define; window.require = require;\n' +
277 278 279 280 281 282
              content +
              '\n}.call(window.__monaco_context__ || (window.__monaco_context__ = {})));'
            );
          }
          return content;
        }
283 284
      }
    ]),
M
Mike Greiling 已提交
285 286 287
  ],

  resolve: {
288
    extensions: ['.js'],
289
    alias: {
290
      '~':              path.join(ROOT_PATH, 'app/assets/javascripts'),
291
      'emojis':         path.join(ROOT_PATH, 'fixtures/emojis'),
292
      'empty_states':   path.join(ROOT_PATH, 'app/views/shared/empty_states'),
293
      'icons':          path.join(ROOT_PATH, 'app/views/shared/icons'),
294
      'images':         path.join(ROOT_PATH, 'app/assets/images'),
295
      'vendor':         path.join(ROOT_PATH, 'vendor/assets/javascripts'),
296
      'vue$':           'vue/dist/vue.esm.js',
297
      'spec':           path.join(ROOT_PATH, 'spec/javascripts'),
298
    }
M
Mike Greiling 已提交
299
  }
300 301
}

302 303
config.entry = Object.assign({}, autoEntries, config.entry);

304
if (IS_PRODUCTION) {
M
Mike Greiling 已提交
305
  config.devtool = 'source-map';
306
  config.plugins.push(
307
    new webpack.NoEmitOnErrorsPlugin(),
M
Mike Greiling 已提交
308 309 310 311
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false
    }),
312
    new webpack.optimize.UglifyJsPlugin({
M
Mike Greiling 已提交
313
      sourceMap: true
314 315 316
    }),
    new webpack.DefinePlugin({
      'process.env': { NODE_ENV: JSON.stringify('production') }
M
Mike Greiling 已提交
317
    })
318
  );
M
Mike Greiling 已提交
319

320
  // compression can require a lot of compute time and is disabled in CI
M
Mike Greiling 已提交
321
  if (!NO_COMPRESSION) {
322
    config.plugins.push(new CompressionPlugin());
M
Mike Greiling 已提交
323
  }
324 325 326
}

if (IS_DEV_SERVER) {
327
  config.devtool = 'cheap-module-eval-source-map';
328
  config.devServer = {
329
    host: DEV_SERVER_HOST,
330
    port: DEV_SERVER_PORT,
331
    disableHostCheck: true,
332 333
    headers: { 'Access-Control-Allow-Origin': '*' },
    stats: 'errors-only',
S
Simon Knox 已提交
334
    hot: DEV_SERVER_LIVERELOAD,
335
    inline: DEV_SERVER_LIVERELOAD
336
  };
337 338 339 340
  config.plugins.push(
    // watch node_modules for changes if we encounter a missing module compile error
    new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
  );
S
Simon Knox 已提交
341 342 343
  if (DEV_SERVER_LIVERELOAD) {
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
  }
344 345
}

346 347 348 349 350 351 352 353 354 355 356 357
if (WEBPACK_REPORT) {
  config.plugins.push(
    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'),
    })
  );
}

358
module.exports = config;