Files
zulip/tools/webpack.config.ts
Anders Kaseorg 68cfcd6446 CVE-2020-9444: Prevent reverse tabnabbing attacks.
While we could fix this issue by changing the markdown processor,
doing so is not a robust solution, because even a momentary bug in the
markdown processor could allow cached messages that do not follow our
security policy.

This change ensures that even if our markdown processor has bugs that
result in rendered content that does not properly follow our policy of
using rel="noopener noreferrer" on links, we'll still do something
reasonable.

Co-authored-by: Tim Abbott <tabbott@zulipchat.com>
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-01 14:01:45 -07:00

264 lines
10 KiB
TypeScript

import { basename, resolve } from 'path';
import { cacheLoader, getExposeLoaders } from './webpack-helpers';
import BundleTracker from 'webpack4-bundle-tracker';
import CleanCss from 'clean-css';
import DebugRequirePlugin from './debug-require-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
// The devServer member of webpack.Configuration is managed by the
// webpack-dev-server package. We are only importing the type here.
import _webpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
const assets: { [name: string]: string[] } = require('./webpack.assets.json');
export default (env?: string): webpack.Configuration[] => {
const production: boolean = env === "production";
const publicPath = production ? '/static/webpack-bundles/' : '/webpack/';
const config: webpack.Configuration = {
name: "frontend",
mode: production ? "production" : "development",
context: resolve(__dirname, "../"),
entry: assets,
module: {
rules: [
// Generate webfont
{
test: /\.font\.js$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'webfonts-loader',
options: {
fileName: production ? 'files/[fontname].[chunkhash].[ext]' : 'files/[fontname].[ext]',
publicPath,
},
},
],
},
// Transpile .js and .ts files with Babel
{
test: /\.(js|ts)$/,
include: [
resolve(__dirname, '../static/shared/js'),
resolve(__dirname, '../static/js'),
],
loader: 'babel-loader',
},
// Uses script-loader on minified files so we don't change global variables in them.
// Also has the effect of making processing these files fast
// Currently the source maps don't work with these so use unminified files
// if debugging is required.
{
// We dont want to match admin.js
test: /(\.min|min\.|zxcvbn)\.js/,
use: [cacheLoader, 'script-loader'],
},
// regular css files
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !production,
},
},
cacheLoader,
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
],
},
// scss loader
{
test: /\.scss$/,
include: resolve(__dirname, '../static/styles'),
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !production,
},
},
cacheLoader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.hbs$/,
loader: 'handlebars-loader',
options: {
// Tell webpack not to explicitly require these.
knownHelpers: ['if', 'unless', 'each', 'with',
// The ones below are defined in static/js/templates.js
'plural', 'eq', 'and', 'or', 'not',
't', 'tr', 'rendered_markdown'],
preventIndent: true,
},
},
// load fonts and files
{
test: /\.(woff(2)?|ttf|eot|svg|otf|png)$/,
use: [{
loader: 'file-loader',
options: {
name: production ? '[name].[hash].[ext]' : '[path][name].[ext]',
outputPath: 'files/',
},
}],
},
],
},
output: {
path: resolve(__dirname, '../static/webpack-bundles'),
publicPath,
filename: production ? '[name].[contenthash].js' : '[name].js',
chunkFilename: production ? '[contenthash].js' : '[id].js',
},
resolve: {
extensions: [".ts", ".js"],
},
// We prefer cheap-module-source-map over any eval-** options
// because the eval-options currently don't support being
// source mapped in error stack traces
// We prefer it over eval since eval has trouble setting
// breakpoints in chrome.
devtool: production ? 'source-map' : 'cheap-module-source-map',
optimization: {
minimizer: [
// Based on a comment in NMFR/optimize-css-assets-webpack-plugin#10.
// Can be simplified when NMFR/optimize-css-assets-webpack-plugin#87
// is fixed.
new OptimizeCssAssetsPlugin({
cssProcessor: {
async process(css, options: any) {
const filename = basename(options.to);
const result = await new CleanCss(options).minify({
[filename]: {
styles: css,
sourceMap: options.map.prev,
},
});
for (const warning of result.warnings) {
console.warn(warning);
}
return {
css: result.styles + `\n/*# sourceMappingURL=${filename}.map */`,
map: result.sourceMap,
};
},
},
cssProcessorOptions: {
map: {},
returnPromise: true,
sourceMap: true,
sourceMapInlineSources: true,
},
}),
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true,
}),
],
splitChunks: {
chunks: "all",
// webpack/examples/many-pages suggests 20 requests for HTTP/2
maxAsyncRequests: 20,
maxInitialRequests: 20,
},
},
plugins: [
new DebugRequirePlugin(),
new BundleTracker({
filename: production
? 'webpack-stats-production.json'
: 'var/webpack-stats-dev.json',
}),
...production
? []
: [
// Better logging from console for hot reload
new webpack.NamedModulesPlugin(),
// script-loader should load sourceURL in dev
new webpack.LoaderOptionsPlugin({debug: true}),
],
// Extract CSS from files
new MiniCssExtractPlugin({
filename: production ? "[name].[contenthash].css" : "[name].css",
chunkFilename: production ? "[contenthash].css" : "[id].css",
}),
new HtmlWebpackPlugin({
filename: "5xx.html",
template: "static/html/5xx.html",
chunks: ["error-styles"],
}),
],
};
// Expose Global variables for third party libraries to webpack modules
// Use the unminified versions of jquery and underscore so that
// Good error messages show up in production and development in the source maps
const exposeOptions = [
{ path: "./debug-require.js", name: "require" },
{ path: "blueimp-md5/js/md5.js" },
{ path: "clipboard/dist/clipboard.js", name: "ClipboardJS" },
{ path: "xdate/src/xdate.js", name: "XDate" },
{ path: "../static/third/marked/lib/marked.js" },
{ path: "../static/js/debug.js" },
{ path: "../static/js/blueslip.js" },
{ path: "../static/js/common.js" },
{ path: "jquery/dist/jquery.js", name: ['$', 'jQuery'] },
{ path: "underscore/underscore.js", name: '_' },
{ path: "handlebars/dist/cjs/handlebars.runtime.js", name: 'Handlebars' },
{ path: "sortablejs/Sortable.js"},
{ path: "winchan/winchan.js", name: 'WinChan'},
];
config.module.rules.unshift(...getExposeLoaders(exposeOptions));
if (!production) {
// Out JS debugging tools
for (const name of Object.keys(config.entry)) {
assets[name].push('./static/js/debug.js');
}
config.devServer = {
clientLogLevel: "error",
stats: "errors-only",
};
}
const serverConfig: webpack.Configuration = {
mode: production ? "production" : "development",
target: "node",
context: resolve(__dirname, "../"),
entry: {
"katex-cli": "shebang-loader!katex/cli",
},
output: {
path: resolve(__dirname, "../static/webpack-bundles"),
filename: "[name].js",
},
};
return [config, serverConfig];
};