element-web-Github/webpack.config.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

854 lines
41 KiB
JavaScript
Raw Normal View History

2021-06-10 22:19:38 +08:00
/* eslint-disable quote-props */
const dotenv = require("dotenv");
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const HtmlWebpackInjectPreload = require("@principalstudio/html-webpack-inject-preload");
const CopyWebpackPlugin = require("copy-webpack-plugin");
// Environment variables
// RIOT_OG_IMAGE_URL: specifies the URL to the image which should be used for the opengraph logo.
// CSP_EXTRA_SOURCE: specifies a URL which should be appended to each CSP directive which uses 'self',
// this can be helpful if your deployment has redirects for old bundles, such as develop.element.io.
2021-08-03 04:27:13 +08:00
dotenv.config();
2021-04-15 22:15:48 +08:00
let ogImageUrl = process.env.RIOT_OG_IMAGE_URL;
if (!ogImageUrl) ogImageUrl = "https://app.element.io/themes/element/img/logos/opengraph.png";
if (!process.env.VERSION) {
console.warn("Unset VERSION variable - this may affect build output");
process.env.VERSION = "!!UNSET!!";
}
const cssThemes = {
// CSS themes
"theme-legacy-light": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.pcss",
"theme-legacy-dark": "./node_modules/matrix-react-sdk/res/themes/legacy-dark/css/legacy-dark.pcss",
"theme-light": "./node_modules/matrix-react-sdk/res/themes/light/css/light.pcss",
"theme-light-high-contrast":
"./node_modules/matrix-react-sdk/res/themes/light-high-contrast/css/light-high-contrast.pcss",
"theme-dark": "./node_modules/matrix-react-sdk/res/themes/dark/css/dark.pcss",
"theme-light-custom": "./node_modules/matrix-react-sdk/res/themes/light-custom/css/light-custom.pcss",
"theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.pcss",
};
function getActiveThemes() {
2021-08-19 00:12:36 +08:00
// Default to `light` theme when the MATRIX_THEMES environment variable is not defined.
const theme = process.env.MATRIX_THEMES ?? "light";
return theme
.split(",")
.map((x) => x.trim())
.filter(Boolean);
}
// See docs/customisations.md
let fileOverrides = {
/* {[file: string]: string} */
};
try {
fileOverrides = require("./customisations.json");
// stringify the output so it appears in logs correctly, as large files can sometimes get
// represented as `<Object>` which is less than helpful.
console.log("Using customisations.json : " + JSON.stringify(fileOverrides, null, 4));
process.on("exit", () => {
console.log(""); // blank line
console.warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.warn("!! Customisations have been deprecated and will be removed in a future release !!");
console.warn("!! See https://github.com/element-hq/element-web/blob/develop/docs/customisations.md !!");
console.warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.log(""); // blank line
});
} catch (e) {
// ignore - not important
}
function parseOverridesToReplacements(overrides) {
return Object.entries(overrides).map(([oldPath, newPath]) => {
return new webpack.NormalModuleReplacementPlugin(
// because the input is effectively defined by the person running the build, we don't
// need to do anything special to protect against regex overrunning, etc.
new RegExp(oldPath.replace(/\//g, "[\\/\\\\]").replace(/\./g, "\\.")),
function (resource) {
resource.request = path.resolve(__dirname, newPath);
resource.createData.resource = path.resolve(__dirname, newPath);
// Starting with Webpack 5 we also need to set the context as otherwise replacing
// files in e.g. matrix-react-sdk with files from element-web will try to resolve
// them within matrix-react-sdk (https://github.com/webpack/webpack/issues/17716)
2023-10-25 23:11:25 +08:00
resource.context = path.dirname(resource.request);
resource.createData.context = path.dirname(resource.createData.resource);
2023-10-25 23:11:25 +08:00
},
);
});
}
const moduleReplacementPlugins = [
...parseOverridesToReplacements(require("./components.json")),
// Allow customisations to override the default components too
...parseOverridesToReplacements(fileOverrides),
];
module.exports = (env, argv) => {
// Establish settings based on the environment and args.
//
// argv.mode is always set to "production" by yarn build
// (called to build prod, nightly and develop.element.io)
// arg.mode is set to "development" by yarn start
// (called by developers, runs the continuous reload script)
// process.env.CI_PACKAGE is set when yarn build is called from scripts/ci_package.sh
// (called to build nightly and develop.element.io)
const nodeEnv = argv.mode;
const devMode = nodeEnv !== "production";
2021-08-13 00:32:13 +08:00
const useHMR = process.env.CSS_HOT_RELOAD === "1" && devMode;
const enableMinification = !devMode && !process.env.CI_PACKAGE;
const development = {};
if (devMode) {
// Embedded source maps for dev builds, can't use eval-source-map due to CSP
development["devtool"] = "inline-source-map";
2020-07-17 21:57:48 +08:00
} else {
if (process.env.CI_PACKAGE) {
// High quality source maps in separate .map files which include the source. This doesn't bulk up the .js
// payload file size, which is nice for performance but also necessary to get the bundle to a small enough
// size that sentry will accept the upload.
development["devtool"] = "source-map";
} else {
// High quality source maps in separate .map files which don't include the source
development["devtool"] = "nosources-source-map";
}
}
// Resolve the directories for the react-sdk and js-sdk for later use. We resolve these early, so we
// don't have to call them over and over. We also resolve to the package.json instead of the src
// directory, so we don't have to rely on an index.js or similar file existing.
const reactSdkSrcDir = path.resolve(require.resolve("matrix-react-sdk/package.json"), "..", "src");
const jsSdkSrcDir = path.resolve(require.resolve("matrix-js-sdk/package.json"), "..", "src");
const ACTIVE_THEMES = getActiveThemes();
function getThemesImports() {
const imports = ACTIVE_THEMES.map((t) => {
return cssThemes[`theme-${t}`].replace("./node_modules/", ""); // theme import path
});
const s = JSON.stringify(ACTIVE_THEMES);
return `
2021-08-03 04:27:13 +08:00
window.MX_insertedThemeStylesCounter = 0;
window.MX_DEV_ACTIVE_THEMES = (${s});
${imports.map((i) => `import("${i}")`).join("\n")};
`;
}
return {
...development,
bail: true,
entry: {
bundle: "./src/vector/index.ts",
mobileguide: "./src/vector/mobile_guide/index.ts",
Use a local widget wrapper for Jitsi calls Effectively fixes https://github.com/vector-im/riot-web/issues/11074 Effectively fixes https://github.com/vector-im/riot-web/issues/7112 Fixes https://github.com/vector-im/riot-web/issues/6930 Fixes Jitsi widgets not working for guests (https://github.com/vector-im/riot-web/issues/8933) Fixes https://github.com/vector-im/riot-web/issues/5048 Previously we were relying on an integration manager to be defined, functional, and alive in order to join Jitsi calls. This commit changes this so we aren't reliant on an integration manager for Jitsi calls at all, and gives people the option of choosing a Jitsi server via the config.json. This side is just the wrapper/shell: the logic is mostly in the react-sdk (to be linked via PRs). This layer simply has an HTML file exported that can be used to render a Jitsi widget, and the react-sdk constructs a URL to access it locally. This is similar to how the mobile apps handle Jitsi widgets: instead of iframing the widget URL directly into the app, they pull apart the widget information and natively render it. We're effectively doing the same here by parsing the widget options and using our local wrapper instead of whatever happens to be defined in the widget state event. Integration managers should still continue to offer a widget URL for Jitsi widgets as this is what the spec requires. A large part of this is based upon Dimension's handling of Jitsi and widgets in general: a license has been granted to allow Riot (and therefore the react-sdk) to use the code and be inspired by it.
2020-03-19 05:47:56 +08:00
jitsi: "./src/vector/jitsi/index.ts",
usercontent: "./node_modules/matrix-react-sdk/src/usercontent/index.ts",
serviceworker: {
import: "./src/serviceworker/index.ts",
filename: "sw.js", // update WebPlatform if this changes
},
2021-08-13 00:32:13 +08:00
...(useHMR ? {} : cssThemes),
},
optimization: {
// Put all of our CSS into one useful place - this is needed for MiniCssExtractPlugin.
// Previously we used a different extraction plugin that did this magic for us, but
// now we need to consider that the CSS needs to be bundled up together.
splitChunks: {
cacheGroups: {
styles: {
name: "styles",
2020-01-17 02:33:55 +08:00
test: /\.css$/,
enforce: true,
// Do not add `chunks: 'all'` here because you'll break the app entry point.
},
// put the unhomoglyph data in its own file. It contains
// magic characters which mess up line numbers in the
// javascript debugger.
unhomoglyph_data: {
name: "unhomoglyph_data",
test: /unhomoglyph\/data\.json$/,
enforce: true,
chunks: "all",
},
default: {
reuseExistingChunk: true,
},
},
},
2023-11-09 22:47:55 +08:00
// Readable IDs for better debugging
2023-09-24 03:34:56 +08:00
moduleIds: "named",
2020-01-17 02:47:16 +08:00
// Minification is normally enabled by default for webpack in production mode, but
// we use a CSS optimizer too and need to manage it ourselves.
minimize: enableMinification,
minimizer: enableMinification
? [
new TerserPlugin({
// Already minified and includes an auto-generated license comment
// that the plugin would otherwise pointlessly extract into a separate
// file. We add the actual license using CopyWebpackPlugin below.
exclude: "jitsi_external_api.min.js",
}),
new CssMinimizerPlugin(),
]
: [],
// Set the value of `process.env.NODE_ENV` for libraries like React
// See also https://v4.webpack.js.org/configuration/optimization/#optimizationnodeenv
nodeEnv,
},
resolve: {
// We define an alternative import path so we can safely use src/ across the react-sdk
// and js-sdk. We already import from src/ where possible to ensure our source maps are
// extremely accurate (and because we're capable of compiling the layers manually rather
// than relying on partially-mangled output from babel), though we do need to fix the
// package level import (stuff like `import {Thing} from "matrix-js-sdk"` for example).
// We can't use the aliasing down below to point at src/ because that'll fail to resolve
// the package.json for the dependency. Instead, we rely on the package.json of each
// layer to have our custom alternate fields to load things in the right order. These are
// the defaults of webpack prepended with `matrix_src_`.
mainFields: ["matrix_src_browser", "matrix_src_main", "browser", "main"],
2020-01-17 02:33:55 +08:00
aliasFields: ["matrix_src_browser", "browser"],
2020-01-17 02:33:55 +08:00
// We need to specify that TS can be resolved without an extension
extensions: [".js", ".json", ".ts", ".tsx"],
2020-01-17 02:33:55 +08:00
alias: {
// alias any requires to the react module to the one in our path,
// otherwise we tend to get the react source included twice when
// using `npm link` / `yarn link`.
"react": path.resolve(__dirname, "node_modules/react"),
2020-01-17 02:33:55 +08:00
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
// Same goes for js/react-sdk - we don't need two copies.
2020-01-17 02:33:55 +08:00
"matrix-js-sdk": path.resolve(__dirname, "node_modules/matrix-js-sdk"),
"matrix-react-sdk": path.resolve(__dirname, "node_modules/matrix-react-sdk"),
"@matrix-org/react-sdk-module-api": path.resolve(
__dirname,
"node_modules/@matrix-org/react-sdk-module-api",
),
2023-07-17 20:15:03 +08:00
// and matrix-events-sdk & matrix-widget-api
"matrix-events-sdk": path.resolve(__dirname, "node_modules/matrix-events-sdk"),
"matrix-widget-api": path.resolve(__dirname, "node_modules/matrix-widget-api"),
2020-01-17 02:33:55 +08:00
// Define a variable so the i18n stuff can load
"$webapp": path.resolve(__dirname, "webapp"),
},
2023-09-24 03:34:56 +08:00
fallback: {
// Mock out the NodeFS module: The opus decoder imports this wrongly.
2023-10-25 23:11:25 +08:00
"fs": false,
"net": false,
"tls": false,
"crypto": false,
2023-09-24 03:34:56 +08:00
// Polyfill needed by counterpart
2023-10-25 23:11:25 +08:00
"util": require.resolve("util/"),
// Polyfill needed by matrix-js-sdk/src/crypto
2023-10-25 23:11:25 +08:00
"buffer": require.resolve("buffer/"),
// Polyfill needed by sentry
2023-10-25 23:11:25 +08:00
"process/browser": require.resolve("process/browser"),
},
},
module: {
noParse: [
// for cross platform compatibility use [\\\/] as the path separator
// this ensures that the regex trips on both Windows and *nix
// don't parse the languages within highlight.js. They cause stack
// overflows (https://github.com/webpack/webpack/issues/1721), and
// there is no need for webpack to parse them - they can just be
// included as-is.
2021-06-10 22:19:38 +08:00
/highlight\.js[\\/]lib[\\/]languages/,
],
rules: [
2021-08-13 00:32:13 +08:00
useHMR && {
test: /devcss\.ts$/,
loader: "string-replace-loader",
options: {
search: '"use theming";',
replace: getThemesImports(),
},
},
{
test: /\.(ts|js)x?$/,
include: (f) => {
2020-01-21 02:20:41 +08:00
// our own source needs babel-ing
if (f.startsWith(path.resolve(__dirname, "src"))) return true;
// we use the original source files of react-sdk and js-sdk, so we need to
2020-01-21 02:20:41 +08:00
// run them through babel. Because the path tested is the resolved, absolute
// path, these could be anywhere thanks to yarn link. We must also not
// include node modules inside these modules, so we add 'src'.
if (f.startsWith(reactSdkSrcDir)) return true;
if (f.startsWith(jsSdkSrcDir)) return true;
2020-01-21 02:20:41 +08:00
// Some of the syntax in this package is not understood by
// either webpack or our babel setup.
// When we do get to upgrade our current setup, this should
// probably be removed.
if (f.includes(path.join("@vector-im", "compound-web"))) return true;
// but we can't run all of our dependencies through babel (many of them still
// use module.exports which breaks if babel injects an 'include' for its
// polyfills: probably fixable but babeling all our dependencies is probably
2020-01-21 02:20:41 +08:00
// not necessary anyway). So, for anything else, don't babel.
return false;
},
loader: "babel-loader",
options: {
2021-04-15 22:15:48 +08:00
cacheDirectory: true,
plugins: enableMinification ? ["babel-plugin-jsx-remove-data-test-id"] : [],
2021-04-15 22:15:48 +08:00
},
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1,
sourceMap: true,
esModule: false,
2021-04-15 22:15:48 +08:00
},
},
{
loader: "postcss-loader",
ident: "postcss",
options: {
sourceMap: true,
postcssOptions: () => ({
"plugins": [
// Note that we use significantly fewer plugins on the plain
// CSS parser. If we start to parse plain CSS, we end with all
// kinds of nasty problems (like stylesheets not loading).
//
// You might have noticed that we're also sending regular CSS
// through PostCSS. This looks weird, and in fact is probably
// not what you'd expect, however in order for our CSS build
// to work nicely we have to do this. Because down the line
// our SCSS stylesheets reference plain CSS we have to load
// the plain CSS through PostCSS so it can find it safely. This
// also acts like a babel-for-css by transpiling our (S)CSS
// down/up to the right browser support (prefixes, etc).
// Further, if we don't do this then PostCSS assumes that our
// plain CSS is SCSS and it really doesn't like that, even
// though plain CSS should be compatible. The chunking options
// at the top of this webpack config help group the SCSS and
// plain CSS together for the bundler.
require("postcss-simple-vars")(),
require("postcss-hexrgba")(),
// It's important that this plugin is last otherwise we end
// up with broken CSS.
require("postcss-preset-env")({ stage: 3, browsers: "last 2 versions" }),
],
"parser": "postcss-scss",
"local-plugins": true,
}),
},
},
2021-04-15 22:15:48 +08:00
],
},
{
test: /\.pcss$/,
use: [
/**
* This code is hopeful that no .pcss outside of our themes will be directly imported in any
* of the JS/TS files.
* Should be MUCH better with webpack 5, but we're stuck to this solution for now.
*/
2021-08-13 00:32:13 +08:00
useHMR
? {
2021-08-03 04:27:13 +08:00
loader: "style-loader",
/**
* If we refactor the `theme.js` in `matrix-react-sdk` a little bit,
* we could try using `lazyStyleTag` here to add and remove styles on demand,
* that would nicely resolve issues of race conditions for themes,
* at least for development purposes.
*/
options: {
insert: function insertBeforeAt(element) {
const parent = document.querySelector("head");
2021-08-03 04:27:13 +08:00
// We're in iframe
if (!window.MX_DEV_ACTIVE_THEMES) {
parent.appendChild(element);
return;
}
2021-08-03 04:27:13 +08:00
// Properly disable all other instances of themes
element.disabled = true;
element.onload = () => {
element.disabled = true;
};
const theme =
window.MX_DEV_ACTIVE_THEMES[window.MX_insertedThemeStylesCounter];
element.setAttribute("data-mx-theme", theme);
window.MX_insertedThemeStylesCounter++;
parent.appendChild(element);
2022-12-09 20:28:29 +08:00
},
},
}
: MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1,
sourceMap: true,
esModule: false,
2021-04-15 22:15:48 +08:00
},
},
{
loader: "postcss-loader",
ident: "postcss",
options: {
sourceMap: true,
postcssOptions: () => ({
"plugins": [
// Note that we use slightly different plugins for PostCSS.
require("postcss-import")(),
require("postcss-mixins")(),
require("postcss-simple-vars")(),
require("postcss-nested")(),
require("postcss-easings")(),
require("postcss-hexrgba")(),
// It's important that this plugin is last otherwise we end
// up with broken CSS.
require("postcss-preset-env")({ stage: 3, browsers: "last 2 versions" }),
],
"parser": "postcss-scss",
"local-plugins": true,
}),
},
},
2021-04-15 22:15:48 +08:00
],
},
{
// Fix up the name of the opus-recorder worker (react-sdk dependency).
// We more or less just want it to be clear it's for opus and not something else.
test: /encoderWorker\.min\.js$/,
loader: "file-loader",
type: "javascript/auto",
options: {
// We deliberately override the name so it makes sense in debugging
name: "opus-encoderWorker.min.[hash:7].[ext]",
outputPath: ".",
},
},
{
// Ideally we should use the built-in worklet support in Webpack 5 with the syntax
// described in https://github.com/webpack/webpack.js.org/issues/6869. However, this
// doesn't currently appear to work with our public path setup. So we handle this
// with a custom loader instead.
test: /RecorderWorklet\.ts$/,
type: "javascript/auto",
use: [
{
loader: path.resolve("./recorder-worklet-loader.js"),
},
{
loader: "babel-loader",
},
],
},
{
// This is from the same place as the encoderWorker above, but only needed
// for Safari support.
test: /decoderWorker\.min\.js$/,
loader: "file-loader",
type: "javascript/auto", // https://github.com/webpack/webpack/issues/6725
options: {
// We deliberately override the name so it makes sense in debugging
name: "opus-decoderWorker.min.[hash:7].[ext]",
outputPath: ".",
},
},
{
// The decoderWorker wants to load its own wasm, rather than have webpack do it.
// We therefore use the `file-loader` to tell webpack to dump the contents to
// a separate file and return the name, and override the default `type` for `.wasm` files
// (which is `webassembly/experimental` under webpack 4) to stop webpack trying to interpret
// the filename as webassembly. (see also https://github.com/webpack/webpack/issues/6725)
test: /decoderWorker\.min\.wasm$/,
loader: "file-loader",
type: "javascript/auto",
options: {
// We deliberately don't change the name because the decoderWorker has this
// hardcoded. This is here to avoid the default wasm rule from adding a hash.
name: "decoderWorker.min.wasm",
outputPath: ".",
},
},
{
// This is from the same place as the encoderWorker above, but only needed
// for Safari support.
test: /waveWorker\.min\.js$/,
loader: "file-loader",
type: "javascript/auto", // https://github.com/webpack/webpack/issues/6725
options: {
// We deliberately override the name so it makes sense in debugging
name: "wave-encoderWorker.min.[hash:7].[ext]",
outputPath: ".",
},
},
{
// cache-bust languages.json file placed in
// element-web/webapp/i18n during build by copy-res.ts
test: /\.*languages.json$/,
type: "javascript/auto",
loader: "file-loader",
options: {
name: "i18n/[name].[hash:7].[ext]",
},
2019-02-20 16:39:42 +08:00
},
{
test: /\.svg$/,
issuer: /\.(js|ts|jsx|tsx|html)$/,
use: [
{
loader: "@svgr/webpack",
options: {
namedExport: "Icon",
svgProps: {
"role": "presentation",
"aria-hidden": true,
},
// props set on the svg will override defaults
expandProps: "end",
svgoConfig: {
plugins: [
{
name: "preset-default",
params: {
overrides: {
removeViewBox: false,
},
},
},
// generates a viewbox if missing
{ name: "removeDimensions" },
// https://github.com/facebook/docusaurus/issues/8297
{ name: "prefixIds" },
],
},
/**
* Forwards the React ref to the root SVG element
* Useful when using things like `asChild` in
* radix-ui
*/
ref: true,
esModule: false,
name: "[name].[hash:7].[ext]",
outputPath: getAssetOutputPath,
publicPath: function (url, resourcePath) {
const outputPath = getAssetOutputPath(url, resourcePath);
return toPublicPath(outputPath);
},
},
},
{
loader: "file-loader",
options: {
esModule: false,
name: "[name].[hash:7].[ext]",
outputPath: getAssetOutputPath,
publicPath: function (url, resourcePath) {
const outputPath = getAssetOutputPath(url, resourcePath);
return toPublicPath(outputPath);
},
},
},
],
},
{
test: /\.svg$/,
issuer: /\.(pcss|scss|css)$/,
use: [
{
loader: "file-loader",
options: {
esModule: false,
name: "[name].[hash:7].[ext]",
outputPath: getAssetOutputPath,
publicPath: function (url, resourcePath) {
// CSS image usages end up in the `bundles/[hash]` output
// directory, so we adjust the final path to navigate up
// twice.
const outputPath = getAssetOutputPath(url, resourcePath);
return toPublicPath(path.join("../..", outputPath));
},
},
},
],
},
{
test: /\.(gif|png|ttf|woff|woff2|xml|ico)$/,
// Use a content-based hash in the name so that we can set a long cache
// lifetime for assets while still delivering changes quickly.
oneOf: [
{
// Assets referenced in CSS files
issuer: /\.(pcss|scss|css)$/,
loader: "file-loader",
options: {
esModule: false,
name: "[name].[hash:7].[ext]",
outputPath: getAssetOutputPath,
publicPath: function (url, resourcePath) {
// CSS image usages end up in the `bundles/[hash]` output
// directory, so we adjust the final path to navigate up
// twice.
const outputPath = getAssetOutputPath(url, resourcePath);
return toPublicPath(path.join("../..", outputPath));
},
},
},
{
// Assets referenced in HTML and JS files
loader: "file-loader",
options: {
esModule: false,
name: "[name].[hash:7].[ext]",
outputPath: getAssetOutputPath,
publicPath: function (url, resourcePath) {
const outputPath = getAssetOutputPath(url, resourcePath);
return toPublicPath(outputPath);
},
},
},
],
},
].filter(Boolean),
},
plugins: [
...moduleReplacementPlugins,
// This exports our CSS using the splitChunks and loaders above.
new MiniCssExtractPlugin({
filename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
chunkFilename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
ignoreOrder: false, // Enable to remove warnings about conflicting order
}),
// This is the app's main entry point.
new HtmlWebpackPlugin({
template: "./src/vector/index.html",
// we inject the links ourselves via the template, because
// HtmlWebpackPlugin will screw up our formatting like the names
// of the themes and which chunks we actually care about.
inject: false,
excludeChunks: ["mobileguide", "usercontent", "jitsi", "serviceworker"],
minify: false,
2021-04-22 17:22:52 +08:00
templateParameters: {
2021-04-15 22:15:48 +08:00
og_image_url: ogImageUrl,
csp_extra_source: process.env.CSP_EXTRA_SOURCE ?? "",
},
}),
// This is the jitsi widget wrapper (embedded, so isolated stack)
new HtmlWebpackPlugin({
template: "./src/vector/jitsi/index.html",
filename: "jitsi.html",
minify: false,
chunks: ["jitsi"],
}),
// This is the mobile guide's entry point (separate for faster mobile loading)
new HtmlWebpackPlugin({
template: "./src/vector/mobile_guide/index.html",
filename: "mobile_guide/index.html",
minify: false,
chunks: ["mobileguide"],
}),
// These are the static error pages for when the javascript env is *really unsupported*
new HtmlWebpackPlugin({
template: "./src/vector/static/unable-to-load.html",
filename: "static/unable-to-load.html",
minify: false,
chunks: [],
}),
new HtmlWebpackPlugin({
template: "./src/vector/static/incompatible-browser.html",
filename: "static/incompatible-browser.html",
minify: false,
chunks: [],
}),
// This is the usercontent sandbox's entry point (separate for iframing)
new HtmlWebpackPlugin({
template: "./node_modules/matrix-react-sdk/src/usercontent/index.html",
filename: "usercontent/index.html",
minify: false,
chunks: ["usercontent"],
}),
new HtmlWebpackInjectPreload({
files: [{ match: /.*Inter.*\.woff2$/ }],
}),
// Upload to sentry if sentry env is present
2024-01-22 16:49:32 +08:00
// This plugin throws an error on import on some platforms like ppc64le & s390x even if the plugin isn't called,
// so we require it conditionally.
process.env.SENTRY_DSN &&
require("@sentry/webpack-plugin").sentryWebpackPlugin({
release: process.env.VERSION,
sourcemaps: {
paths: "./webapp/bundles/**",
},
errorHandler: (err) => {
console.warn("Sentry CLI Plugin: " + err.message);
console.log(`::warning title=Sentry error::${err.message}`);
},
}),
2023-09-24 03:34:56 +08:00
new webpack.EnvironmentPlugin(["VERSION"]),
2023-09-24 03:34:56 +08:00
new CopyWebpackPlugin({
patterns: [
"res/apple-app-site-association",
{ from: ".well-known/**", context: path.resolve(__dirname, "res") },
"res/jitsi_external_api.min.js",
"res/jitsi_external_api.min.js.LICENSE.txt",
"res/manifest.json",
"res/welcome.html",
{ from: "welcome/**", context: path.resolve(__dirname, "res") },
{ from: "themes/**", context: path.resolve(__dirname, "res") },
{ from: "vector-icons/**", context: path.resolve(__dirname, "res") },
{ from: "decoder-ring/**", context: path.resolve(__dirname, "res") },
{ from: "media/**", context: path.resolve(__dirname, "node_modules/matrix-react-sdk/res/") },
2023-11-16 21:45:31 +08:00
{ from: "config.json", noErrorOnMissing: true },
"contribute.json",
],
}),
// Automatically load buffer & process modules as we use them without explicitly
// importing them
2023-09-24 03:34:56 +08:00
new webpack.ProvidePlugin({
2023-10-25 23:11:25 +08:00
Buffer: ["buffer", "Buffer"],
process: "process/browser",
2023-09-24 03:34:56 +08:00
}),
].filter(Boolean),
2020-01-17 02:33:55 +08:00
output: {
path: path.join(__dirname, "webapp"),
// The generated JS (and CSS, from the extraction plugin) are put in a
// unique subdirectory for the build. There will only be one such
// 'bundle' directory in the generated tarball; however, hosting
// servers can collect 'bundles' from multiple versions into one
// directory and symlink it into place - this allows users who loaded
// an older version of the application to continue to access webpack
// chunks even after the app is redeployed.
filename: "bundles/[fullhash]/[name].js",
chunkFilename: "bundles/[fullhash]/[name].js",
webassemblyModuleFilename: "bundles/[fullhash]/[modulehash].wasm",
2020-01-17 02:33:55 +08:00
},
// configuration for the webpack-dev-server
devServer: {
2023-11-09 23:11:49 +08:00
client: {
overlay: {
// Only show overlay on build errors as anything more can get annoying quickly
errors: true,
2023-11-10 00:05:41 +08:00
warnings: false,
2023-11-09 23:11:49 +08:00
runtimeErrors: false,
2023-11-10 00:05:41 +08:00
},
2023-11-09 23:11:49 +08:00
},
2023-09-23 02:57:00 +08:00
static: {
// Where to serve static assets from
directory: "./webapp",
},
devMiddleware: {
// Only output errors, warnings, or new compilations.
// This hides the massive list of modules.
stats: "minimal",
},
// Enable Hot Module Replacement without page refresh as a fallback in
// case of build failures
hot: "only",
// Disable host check
2023-09-23 03:03:33 +08:00
allowedHosts: "all",
},
};
};
/**
* Merge assets found via CSS and imports into a single tree, while also preserving
* directories under e.g. `res` or similar.
*
* @param {string} url The adjusted name of the file, such as `warning.1234567.svg`.
* @param {string} resourcePath The absolute path to the source file with unmodified name.
* @return {string} The returned paths will look like `img/warning.1234567.svg`.
*/
function getAssetOutputPath(url, resourcePath) {
2023-08-18 16:45:32 +08:00
const isKaTeX = resourcePath.includes("KaTeX");
// `res` is the parent dir for our own assets in various layers
// `dist` is the parent dir for KaTeX assets
const prefix = /^.*[/\\](dist|res)[/\\]/;
2023-08-18 16:45:32 +08:00
/**
* Only needed for https://github.com/element-hq/element-web/pull/15939
2023-08-18 16:45:32 +08:00
* If keeping this, we are not able to load external assets such as SVG
* images coming from @vector-im/compound-web.
*/
if (isKaTeX && !resourcePath.match(prefix)) {
throw new Error(`Unexpected asset path: ${resourcePath}`);
}
let outputDir = path.dirname(resourcePath).replace(prefix, "");
/**
* Imports from Compound are "absolute", we need to strip out the prefix
* coming before the npm package name.
*
* This logic is scoped to compound packages for now as they are the only
* package that imports external assets. This might need to be made more
* generic in the future
*/
const compoundImportsPrefix = /@vector-im(?:\\|\/)compound-(.*?)(?:\\|\/)/;
const compoundMatch = outputDir.match(compoundImportsPrefix);
if (compoundMatch) {
outputDir = outputDir.substring(compoundMatch.index + compoundMatch[0].length);
}
2023-08-18 16:45:32 +08:00
if (isKaTeX) {
// Add a clearly named directory segment, rather than leaving the KaTeX
// assets loose in each asset type directory.
outputDir = path.join(outputDir, "KaTeX");
}
return path.join(outputDir, path.basename(url));
}
2019-01-19 07:29:25 +08:00
/**
* Convert path to public path format, which always uses forward slashes, since it will
* be placed directly into things like CSS files.
*
* @param {string} path Some path to a file.
2021-04-15 22:15:48 +08:00
* @returns {string} converted path
2019-01-19 07:29:25 +08:00
*/
function toPublicPath(path) {
return path.replace(/\\/g, "/");
}