webpack.common.config.ts

Summary

Maintainability
D
1 day
Test Coverage
// Copyright 2019 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview General config file for Webpack.
 */

const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackRTLPlugin = require('webpack-rtl-plugin');
var path = require('path');
var webpack = require('webpack');
const macros = require('./webpack.common.macros.ts');
var analyticsConstants = require('./assets/analytics-constants.json');

var htmlMinifyConfig = {
  ignoreCustomFragments: [/<\[[\s\S]*?\]>/],
  removeAttributeQuotes: false,
  caseSensitive: true,
  customAttrSurround: [
    [/#/, /(?:)/],
    [/\*/, /(?:)/],
    [/\[?\(?/, /(?:)/],
  ],
  customAttrAssign: [/\)?\]?=/],
};
var commonPrefix = './core/templates';
var defaultMeta = {
  name: 'Personalized Online Learning from Oppia',
  description:
    'Oppia is a free, open-source learning platform. Join ' +
    'the community to create or try an exploration today!',
};

module.exports = {
  resolve: {
    modules: [
      path.resolve(__dirname, 'assets'),
      path.resolve(__dirname, 'core/templates'),
      path.resolve(__dirname, 'extensions'),
      path.resolve(__dirname, 'node_modules'),
      path.resolve(__dirname, 'third_party'),
    ],
    extensions: ['.ts', '.js', '.json', '.html', '.svg', '.png'],
    alias: {
      '@angular/upgrade/static':
        '@angular/upgrade/bundles/upgrade-static.umd.js',
      // These both are used so that we can refer to them in imports using their
      // full path: 'assets/{{filename}}'.
      'assets/constants': 'constants.ts',
      'assets/rich_text_components_definitions':
        'rich_text_components_definitions.ts',
    },
  },
  entry: {
    collection_player:
      commonPrefix +
      '/pages/collection-player-page/' +
      'collection-player-page.import.ts',
    console_errors: commonPrefix + '/tests/console_errors.import.ts',
    creator_dashboard:
      commonPrefix +
      '/pages/creator-dashboard-page/' +
      'creator-dashboard-page.import.ts',
    contributor_dashboard:
      commonPrefix +
      '/pages/contributor-dashboard-page/' +
      'contributor-dashboard-page.import.ts',
    email_dashboard_result:
      commonPrefix +
      '/pages/email-dashboard-pages/email-dashboard-result.import.ts',
    error: commonPrefix + '/pages/error-pages/error-page.import.ts',
    error_iframed:
      commonPrefix +
      '/pages/error-pages/error-iframed-page/' +
      'error-iframed-page.import.ts',
    exploration_editor:
      commonPrefix +
      '/pages/exploration-editor-page/' +
      'exploration-editor-page.import.ts',
    maintenance:
      commonPrefix + '/pages/maintenance-page/maintenance-page.import.ts',
    oppia_root: commonPrefix + '/pages/oppia-root/index.ts',
    lightweight_oppia_root:
      commonPrefix + '/pages/lightweight-oppia-root/index.ts',
    practice_session:
      commonPrefix +
      '/pages/practice-session-page/practice-session-page.import.ts',
    review_test:
      commonPrefix + '/pages/review-test-page/review-test-page.import.ts',
    skill_editor:
      commonPrefix + '/pages/skill-editor-page/skill-editor-page.import.ts',
    story_editor:
      commonPrefix + '/pages/story-editor-page/story-editor-page.import.ts',
    subtopic_viewer:
      commonPrefix +
      '/pages/subtopic-viewer-page/subtopic-viewer-page.import.ts',
    topic_editor:
      commonPrefix + '/pages/topic-editor-page/topic-editor-page.import.ts',
    topics_and_skills_dashboard:
      commonPrefix +
      '/pages/topics-and-skills-dashboard-page/' +
      'topics-and-skills-dashboard-page.import.ts',
    topic_viewer:
      commonPrefix + '/pages/topic-viewer-page/topic-viewer-page.import.ts',
  },

  /**
   * TODO(#13079): Remove the hybrid field from the html webpack plugin options
   * once angularjs is removed from corresponding pages.
   */
  plugins: [
    new webpack.DefinePlugin({
      CAN_SEND_ANALYTICS_EVENTS: analyticsConstants.CAN_SEND_ANALYTICS_EVENTS,
    }),
    new webpack.ProvidePlugin({
      diff_match_patch: [
        'diff_match_patch/lib/diff_match_patch',
        'diff_match_patch',
      ],
      DIFF_EQUAL: ['diff_match_patch/lib/diff_match_patch', 'DIFF_EQUAL'],
      DIFF_INSERT: ['diff_match_patch/lib/diff_match_patch', 'DIFF_INSERT'],
      DIFF_DELETE: ['diff_match_patch/lib/diff_match_patch', 'DIFF_DELETE'],
    }),
    new HtmlWebpackPlugin({
      chunks: ['console_errors'],
      filename: 'console_errors.html',
      hybrid: true,
      meta: {
        name: defaultMeta.name,
        description:
          'Contact the Oppia team, submit feedback, and learn ' +
          'how to get involved with the Oppia project.',
      },
      template: commonPrefix + '/tests/console_errors.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['creator_dashboard'],
      filename: 'creator-dashboard-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/creator-dashboard-page/' +
        'creator-dashboard-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['contributor_dashboard'],
      filename: 'contributor-dashboard-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/contributor-dashboard-page/' +
        'contributor-dashboard-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['email_dashboard_result'],
      filename: 'email-dashboard-result.mainpage.html',
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/email-dashboard-pages/email-dashboard-result.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['error_iframed'],
      filename: 'error-iframed.mainpage.html',
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/error-pages/error-iframed-page/' +
        'error-iframed.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['error'],
      filename: 'error-page-400.mainpage.html',
      meta: defaultMeta,
      template: commonPrefix + '/pages/error-pages/error-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
      statusCode: 400,
    }),
    new HtmlWebpackPlugin({
      chunks: ['error'],
      filename: 'error-page-401.mainpage.html',
      meta: defaultMeta,
      template: commonPrefix + '/pages/error-pages/error-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
      statusCode: 401,
    }),
    new HtmlWebpackPlugin({
      chunks: ['error'],
      filename: 'error-page-500.mainpage.html',
      meta: defaultMeta,
      template: commonPrefix + '/pages/error-pages/error-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
      statusCode: 500,
    }),
    new HtmlWebpackPlugin({
      chunks: ['exploration_editor'],
      filename: 'exploration-editor-page.mainpage.html',
      hybrid: true,
      meta: {
        name: defaultMeta.name,
        description:
          'Help others learn new things. Create lessons through ' +
          'explorations and share your knowledge with the community.',
      },
      template:
        commonPrefix +
        '/pages/exploration-editor-page/' +
        'exploration-editor-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['maintenance'],
      filename: 'maintenance-page.mainpage.html',
      meta: defaultMeta,
      template:
        commonPrefix + '/pages/maintenance-page/maintenance-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['oppia_root'],
      filename: 'oppia-root.mainpage.html',
      meta: defaultMeta,
      template: commonPrefix + '/pages/oppia-root/oppia-root.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['lightweight_oppia_root'],
      filename: 'lightweight-oppia-root.mainpage.html',
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/lightweight-oppia-root/lightweight-oppia-root.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
      lightweight: true,
    }),
    new HtmlWebpackPlugin({
      chunks: ['practice_session'],
      filename: 'practice-session-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/practice-session-page/' +
        'practice-session-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['review_test'],
      filename: 'review-test-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix + '/pages/review-test-page/review-test-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['skill_editor'],
      filename: 'skill-editor-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/skill-editor-page/' +
        'skill-editor-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['story_editor'],
      filename: 'story-editor-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/story-editor-page/' +
        'story-editor-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['subtopic_viewer'],
      filename: 'subtopic-viewer-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/subtopic-viewer-page/' +
        'subtopic-viewer-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['topic_editor'],
      filename: 'topic-editor-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/topic-editor-page/' +
        'topic-editor-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['topics_and_skills_dashboard'],
      filename: 'topics-and-skills-dashboard-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/topics-and-skills-dashboard-page/' +
        'topics-and-skills-dashboard-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new HtmlWebpackPlugin({
      chunks: ['topic_viewer'],
      filename: 'topic-viewer-page.mainpage.html',
      hybrid: true,
      meta: defaultMeta,
      template:
        commonPrefix +
        '/pages/topic-viewer-page/' +
        'topic-viewer-page.mainpage.html',
      minify: htmlMinifyConfig,
      inject: false,
    }),
    new CleanWebpackPlugin({
      cleanAfterEveryBuildPatterns: ['**/*', '!*.html'],
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        macros: {
          load: macros.load,
          loadExtensions: macros.loadExtensions,
        },
      },
    }),
    // Here we insert the CSS files depending on key-value stored on
    // the local storage. This only works for dynamically inserted bundles.
    // For statically inserted bundles we handle this logic in
    // core/templates/pages/footer_js_libs.html.
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[id].[contenthash].css',
      ignoreOrder: false,
      insert: function (linkTag) {
        if (localStorage.getItem('direction') === 'rtl') {
          linkTag.href = linkTag.href.replace('.css', '.rtl.css');
        }
        document.head.appendChild(linkTag);
      },
    }),
    // This generates the RTL version for all CSS bundles.
    new WebpackRTLPlugin({
      minify: {
        zindex: false,
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.ts$/,
        include: [
          path.resolve(__dirname, 'assets'),
          path.resolve(__dirname, 'core/templates'),
          path.resolve(__dirname, 'extensions'),
          path.resolve(__dirname, 'typings'),
        ],
        use: [
          'cache-loader',
          {
            loader: 'ts-loader',
            options: {
              // Typescript checks do the type checking.
              transpileOnly: true,
            },
          },
          {
            loader: path.resolve(
              'angular-template-style-url-replacer.webpack-loader'
            ),
          },
        ],
      },
      {
        test: {
          include: /.html$/,
          exclude: /(directive|component)\.html$/,
        },
        loader: ['cache-loader', 'underscore-template-loader'],
      },
      {
        test: /(directive|component)\.html$/,
        use: [
          'cache-loader',
          {
            loader: 'html-loader',
            options: {
              attributes: false,
              minimize: htmlMinifyConfig,
            },
          },
        ],
      },
      {
        test: /\.css$/,
        include: [
          path.resolve(__dirname, 'core/templates'),
          path.resolve(__dirname, 'extensions'),
          path.resolve(__dirname, 'node_modules'),
        ],
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              url: false,
            },
          },
        ],
      },
    ],
  },
  externals: {
    jquery: 'jQuery',
  },
  optimization: {
    runtimeChunk: 'single',
    sideEffects: true,
    usedExports: true,
    splitChunks: {
      chunks: 'all',
    },
  },
};