gitea/webpack.config.js
silverwind 99d9b0f728
Move serviceworker to workbox and fix SSE interference
Instead of statically hardcoding every frontend asset, this uses a
type-based approach to cache all js,css and manifest.json requests.

This also fixes the issue that the service worker was interfering with
EventSource because it was unconditionally handling all requests which
this new implementation doesn't.

Fixes: https://github.com/go-gitea/gitea/issues/11092
Fixes: https://github.com/go-gitea/gitea/issues/7372
2020-05-21 23:27:26 +02:00

269 lines
7.1 KiB
JavaScript

const cssnano = require('cssnano');
const fastGlob = require('fast-glob');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const PostCSSPresetEnv = require('postcss-preset-env');
const PostCSSSafeParser = require('postcss-safe-parser');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
const TerserPlugin = require('terser-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const {statSync} = require('fs');
const {resolve, parse} = require('path');
const {SourceMapDevToolPlugin} = require('webpack');
const glob = (pattern) => fastGlob.sync(pattern, {cwd: __dirname, absolute: true});
const themes = {};
for (const path of glob('web_src/less/themes/*.less')) {
themes[parse(path).name] = [path];
}
const isProduction = process.env.NODE_ENV !== 'development';
module.exports = {
mode: isProduction ? 'production' : 'development',
entry: {
index: [
resolve(__dirname, 'web_src/js/index.js'),
resolve(__dirname, 'web_src/less/index.less'),
],
swagger: [
resolve(__dirname, 'web_src/js/standalone/swagger.js'),
],
jquery: [
resolve(__dirname, 'web_src/js/jquery.js'),
],
serviceworker: [
resolve(__dirname, 'web_src/js/serviceworker.js'),
],
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
...themes,
},
devtool: false,
output: {
path: resolve(__dirname, 'public'),
filename: ({chunk}) => {
// serviceworker can only manage assets below it's script path so we
// have to put it in / instead of /js/
return chunk.id === 'serviceworker' ? '[name].js' : 'js/[name].js';
},
chunkFilename: 'js/[name].js',
},
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
sourceMap: true,
extractComments: false,
terserOptions: {
keep_fnames: /^(HTML|SVG)/, // https://github.com/fgnass/domino/issues/144
output: {
comments: false,
},
},
}),
new OptimizeCSSAssetsPlugin({
cssProcessor: cssnano,
cssProcessorOptions: {
parser: PostCSSSafeParser,
},
cssProcessorPluginOptions: {
preset: [
'default',
{
discardComments: {
removeAll: true,
},
},
],
},
}),
],
splitChunks: {
chunks: 'async',
name: (_, chunks) => chunks.map((item) => item.name).join('-'),
cacheGroups: {
// this bundles all monaco's languages into one file instead of emitting 1-65.js files
monaco: {
test: /monaco-editor/,
name: 'monaco',
chunks: 'async'
}
}
}
},
module: {
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
},
{
test: require.resolve('jquery-datetimepicker'),
use: 'imports-loader?define=>false,exports=>false',
},
{
test: /\.worker\.js$/,
exclude: /monaco/,
use: [
{
loader: 'worker-loader',
options: {
name: '[name].js',
inline: true,
fallback: false,
},
},
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
cacheIdentifier: [
resolve(__dirname, 'package.json'),
resolve(__dirname, 'package-lock.json'),
resolve(__dirname, 'webpack.config.js'),
].map((path) => statSync(path).mtime.getTime()).join(':'),
sourceMaps: true,
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
regenerator: true,
}
],
'@babel/plugin-proposal-object-rest-spread',
],
},
},
],
},
{
test: /\.(less|css)$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
importLoaders: 2,
url: (_url, resourcePath) => {
// only resolve URLs for dependencies
return resourcePath.includes('node_modules');
},
}
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
PostCSSPresetEnv(),
],
},
},
{
loader: 'less-loader',
},
],
},
{
test: /\.svg$/,
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
spriteFilename: 'img/svg/icons.svg',
symbolId: (path) => {
const {name} = parse(path);
if (/@primer[/\\]octicons/.test(path)) {
return `octicon-${name}`;
}
return name;
},
},
},
{
loader: 'svgo-loader',
},
],
},
{
test: /\.(ttf|woff2?)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
publicPath: (url) => `../fonts/${url}`, // seems required for monaco's font
},
},
],
},
],
},
plugins: [
new VueLoaderPlugin(),
// avoid generating useless js output files for css- and svg-only chunks
new FixStyleOnlyEntriesPlugin({
extensions: ['less', 'scss', 'css', 'svg'],
silent: true,
}),
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: 'css/[name].css',
}),
new SourceMapDevToolPlugin({
filename: 'js/[name].js.map',
include: [
'js/index.js',
],
}),
new SpriteLoaderPlugin({
plainSprite: true,
}),
new MonacoWebpackPlugin({
filename: 'js/monaco-[name].worker.js',
}),
],
performance: {
hints: false,
maxEntrypointSize: Infinity,
maxAssetSize: Infinity,
},
resolve: {
symlinks: false,
alias: {
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only
},
},
watchOptions: {
ignored: [
'node_modules/**',
],
},
stats: {
children: false,
},
};