mirror of
https://github.com/strapi/strapi.git
synced 2025-08-29 03:05:55 +00:00
Merge pull request #12882 from strapi/enhancement/speed-up-webpack
Enhancement/speed up webpack
This commit is contained in:
commit
2627aa6ee2
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "getstarted",
|
"name": "getstarted",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "4.2.0-beta.0",
|
"version": "4.1.4",
|
||||||
"description": "A Strapi application.",
|
"description": "A Strapi application.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"develop": "strapi develop",
|
"develop": "strapi develop",
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"develop:webpack": "cross-env NODE_ENV=development webpack serve --config webpack.config.dev.js --progress profile",
|
"develop:webpack": "cross-env NODE_ENV=development webpack serve --config webpack.config.dev.js --progress profile",
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"build": "rimraf build && node ./scripts/build.js",
|
"build": "rimraf build && node ./scripts/build.js",
|
||||||
|
"build:mesure": "rimraf build && cross-env MESURE_BUILD_SPEED=true node ./scripts/build.js",
|
||||||
"test": "echo \"no tests yet\"",
|
"test": "echo \"no tests yet\"",
|
||||||
"test:unit": "jest --verbose",
|
"test:unit": "jest --verbose",
|
||||||
"test:front": "cross-env IS_EE=true jest --config ./jest.config.front.js",
|
"test:front": "cross-env IS_EE=true jest --config ./jest.config.front.js",
|
||||||
@ -37,10 +38,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.16.7",
|
"@babel/core": "7.16.7",
|
||||||
"@babel/plugin-proposal-async-generator-functions": "7.16.7",
|
|
||||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
|
||||||
"@babel/plugin-transform-modules-commonjs": "7.16.7",
|
|
||||||
"@babel/plugin-transform-runtime": "7.16.7",
|
"@babel/plugin-transform-runtime": "7.16.7",
|
||||||
"@babel/preset-env": "7.16.7",
|
"@babel/preset-env": "7.16.7",
|
||||||
"@babel/preset-react": "7.16.7",
|
"@babel/preset-react": "7.16.7",
|
||||||
@ -131,7 +128,6 @@
|
|||||||
"sift": "13.5.0",
|
"sift": "13.5.0",
|
||||||
"style-loader": "3.3.1",
|
"style-loader": "3.3.1",
|
||||||
"styled-components": "^5.2.3",
|
"styled-components": "^5.2.3",
|
||||||
"terser-webpack-plugin": "5.3.0",
|
|
||||||
"webpack": "5.65.0",
|
"webpack": "5.65.0",
|
||||||
"webpack-cli": "4.9.1",
|
"webpack-cli": "4.9.1",
|
||||||
"webpack-dev-server": "4.7.3",
|
"webpack-dev-server": "4.7.3",
|
||||||
@ -140,6 +136,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"duplicate-dependencies-webpack-plugin": "0.2.0",
|
"duplicate-dependencies-webpack-plugin": "0.2.0",
|
||||||
|
"speed-measure-webpack-plugin": "1.5.0",
|
||||||
"webpack-bundle-analyzer": "4.4.1"
|
"webpack-bundle-analyzer": "4.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||||
|
|
||||||
const webpackConfig = require('../webpack.config');
|
const webpackConfig = require('../webpack.config');
|
||||||
|
const getPluginsPath = require('../utils/get-plugins-path');
|
||||||
const {
|
const {
|
||||||
getCorePluginsPath,
|
getCorePluginsPath,
|
||||||
getPluginToInstallPath,
|
getPluginToInstallPath,
|
||||||
@ -11,20 +14,24 @@ const {
|
|||||||
|
|
||||||
const PLUGINS_TO_INSTALL = ['i18n', 'users-permissions'];
|
const PLUGINS_TO_INSTALL = ['i18n', 'users-permissions'];
|
||||||
|
|
||||||
|
// Wrapper that outputs the webpack speed
|
||||||
|
const smp = new SpeedMeasurePlugin();
|
||||||
|
|
||||||
const buildAdmin = async () => {
|
const buildAdmin = async () => {
|
||||||
const entry = path.join(__dirname, '..', 'admin', 'src');
|
const entry = path.join(__dirname, '..', 'admin', 'src');
|
||||||
const dest = path.join(__dirname, '..', 'build');
|
const dest = path.join(__dirname, '..', 'build');
|
||||||
const corePlugins = getCorePluginsPath();
|
const corePlugins = getCorePluginsPath();
|
||||||
const plugins = getPluginToInstallPath(PLUGINS_TO_INSTALL);
|
const plugins = getPluginToInstallPath(PLUGINS_TO_INSTALL);
|
||||||
const allPlugins = { ...corePlugins, ...plugins };
|
const allPlugins = { ...corePlugins, ...plugins };
|
||||||
|
const pluginsPath = getPluginsPath();
|
||||||
|
|
||||||
await createPluginsFile(allPlugins);
|
await createPluginsFile(allPlugins);
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
entry,
|
entry,
|
||||||
dest,
|
dest,
|
||||||
cacheDir: __dirname,
|
cacheDir: path.join(__dirname, '..'),
|
||||||
pluginsPath: [path.resolve(__dirname, '../../../../packages')],
|
pluginsPath,
|
||||||
env: 'production',
|
env: 'production',
|
||||||
optimize: true,
|
optimize: true,
|
||||||
options: {
|
options: {
|
||||||
@ -33,7 +40,10 @@ const buildAdmin = async () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const compiler = webpack(webpackConfig(args));
|
const config =
|
||||||
|
process.env.MESURE_BUILD_SPEED === 'true' ? smp.wrap(webpackConfig(args)) : webpackConfig(args);
|
||||||
|
|
||||||
|
const compiler = webpack(config);
|
||||||
|
|
||||||
console.log('Building the admin panel');
|
console.log('Building the admin panel');
|
||||||
|
|
||||||
@ -73,7 +83,7 @@ buildAdmin()
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
process.exit();
|
process.exit();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
23
packages/core/admin/utils/__tests__/get-plugins-path.test.js
Normal file
23
packages/core/admin/utils/__tests__/get-plugins-path.test.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const getPluginsPath = require('../get-plugins-path');
|
||||||
|
|
||||||
|
describe('getPluginsPath', () => {
|
||||||
|
test('should return an array of directories that contains an admin/src/index.js file', () => {
|
||||||
|
const results = getPluginsPath();
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
// Test that the content-type-builder is included
|
||||||
|
expect(results.findIndex(p => p.includes('/core/content-type-builder/admin'))).not.toEqual(-1);
|
||||||
|
// Test that the upload is included
|
||||||
|
expect(results.findIndex(p => p.includes('/core/upload/admin'))).not.toEqual(-1);
|
||||||
|
// Test that the documentation is included
|
||||||
|
expect(results.findIndex(p => p.includes('/plugins/documentation/admin'))).not.toEqual(-1);
|
||||||
|
// Test that the CM is not included
|
||||||
|
expect(results.findIndex(p => p.includes('/core/content-manager/admin'))).toEqual(-1);
|
||||||
|
// Test that the admin package is not included
|
||||||
|
expect(results.findIndex(p => p.includes('/core/admin/admin'))).toEqual(-1);
|
||||||
|
// Test that the helper-plugin package is not included
|
||||||
|
expect(results.findIndex(p => p.includes('helper-plugin'))).toEqual(-1);
|
||||||
|
});
|
||||||
|
});
|
26
packages/core/admin/utils/get-plugins-path.js
Normal file
26
packages/core/admin/utils/get-plugins-path.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { join, resolve } = require('path');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
// eslint-disable-next-line node/no-extraneous-require
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
const getPluginsPath = () => {
|
||||||
|
const rootPath = resolve(__dirname, '..', join('..', '..', '..', 'packages'));
|
||||||
|
const corePath = join(rootPath, 'core', '*');
|
||||||
|
const pluginsPath = join(rootPath, 'plugins', '*');
|
||||||
|
const corePackageDirs = glob.sync(corePath);
|
||||||
|
const pluginsPackageDirs = glob.sync(pluginsPath);
|
||||||
|
|
||||||
|
const packageDirs = [...corePackageDirs, ...pluginsPackageDirs].filter(dir => {
|
||||||
|
const isCoreAdmin = dir.includes('packages/core/admin');
|
||||||
|
const pathToEntryPoint = join(dir, 'admin', 'src', 'index.js');
|
||||||
|
const doesAdminFolderExist = fs.pathExistsSync(pathToEntryPoint);
|
||||||
|
|
||||||
|
return !isCoreAdmin && doesAdminFolderExist;
|
||||||
|
});
|
||||||
|
|
||||||
|
return packageDirs.map(dir => resolve(__dirname, '..', join(dir, 'admin', 'src')));
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getPluginsPath;
|
@ -3,7 +3,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
const { DuplicateReporterPlugin } = require('duplicate-dependencies-webpack-plugin');
|
const { DuplicateReporterPlugin } = require('duplicate-dependencies-webpack-plugin');
|
||||||
|
const getPluginsPath = require('./utils/get-plugins-path');
|
||||||
const webpackConfig = require('./webpack.config');
|
const webpackConfig = require('./webpack.config');
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
@ -19,11 +19,12 @@ module.exports = () => {
|
|||||||
backend: 'http://localhost:1337',
|
backend: 'http://localhost:1337',
|
||||||
adminPath: '/admin/',
|
adminPath: '/admin/',
|
||||||
};
|
};
|
||||||
|
const pluginsPath = getPluginsPath();
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
entry,
|
entry,
|
||||||
cacheDir: __dirname,
|
cacheDir: __dirname,
|
||||||
pluginsPath: [path.resolve(__dirname, '../../../packages')],
|
pluginsPath,
|
||||||
dest,
|
dest,
|
||||||
env,
|
env,
|
||||||
options,
|
options,
|
||||||
@ -41,27 +42,6 @@ module.exports = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
snapshot: {
|
|
||||||
managedPaths: [
|
|
||||||
path.resolve(__dirname, '../content-type-builder'),
|
|
||||||
path.resolve(__dirname, '../upload'),
|
|
||||||
path.resolve(__dirname, '../helper-plugin'),
|
|
||||||
],
|
|
||||||
buildDependencies: {
|
|
||||||
hash: true,
|
|
||||||
timestamp: true,
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
timestamp: true,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
timestamp: true,
|
|
||||||
},
|
|
||||||
resolveBuildDependencies: {
|
|
||||||
hash: true,
|
|
||||||
timestamp: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 4000,
|
port: 4000,
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fse = require('fs-extra');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
|
const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const { ESBuildMinifyPlugin } = require('esbuild-loader');
|
||||||
const WebpackBar = require('webpackbar');
|
const WebpackBar = require('webpackbar');
|
||||||
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
||||||
const isWsl = require('is-wsl');
|
|
||||||
const alias = require('./webpack.alias');
|
const alias = require('./webpack.alias');
|
||||||
const getClientEnvironment = require('./env');
|
const getClientEnvironment = require('./env');
|
||||||
|
|
||||||
@ -79,6 +79,11 @@ module.exports = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directly inject a polyfill in the webpack entry point before the entry point
|
||||||
|
// FIXME: I have noticed a bug regarding the helper-plugin and esbuild-loader
|
||||||
|
// The only I could fix it was to inject the babel polyfill
|
||||||
|
const babelPolyfill = '@babel/polyfill/dist/polyfill.min.js';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mode: isProduction ? 'production' : 'development',
|
mode: isProduction ? 'production' : 'development',
|
||||||
bail: isProduction ? true : false,
|
bail: isProduction ? true : false,
|
||||||
@ -86,7 +91,7 @@ module.exports = ({
|
|||||||
experiments: {
|
experiments: {
|
||||||
topLevelAwait: true,
|
topLevelAwait: true,
|
||||||
},
|
},
|
||||||
entry,
|
entry: [babelPolyfill, entry],
|
||||||
output: {
|
output: {
|
||||||
path: dest,
|
path: dest,
|
||||||
publicPath: options.adminPath,
|
publicPath: options.adminPath,
|
||||||
@ -98,28 +103,9 @@ module.exports = ({
|
|||||||
optimization: {
|
optimization: {
|
||||||
minimize: optimize,
|
minimize: optimize,
|
||||||
minimizer: [
|
minimizer: [
|
||||||
// Copied from react-scripts
|
new ESBuildMinifyPlugin({
|
||||||
new TerserPlugin({
|
target: 'es2015',
|
||||||
terserOptions: {
|
css: true, // Apply minification to CSS assets
|
||||||
parse: {
|
|
||||||
ecma: 8,
|
|
||||||
},
|
|
||||||
compress: {
|
|
||||||
ecma: 5,
|
|
||||||
warnings: false,
|
|
||||||
comparisons: false,
|
|
||||||
inline: 2,
|
|
||||||
},
|
|
||||||
mangle: {
|
|
||||||
safari10: true,
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
ecma: 5,
|
|
||||||
comments: false,
|
|
||||||
ascii_only: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parallel: !isWsl,
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
runtimeChunk: true,
|
runtimeChunk: true,
|
||||||
@ -128,41 +114,79 @@ module.exports = ({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.m?js$/,
|
test: /\.m?js$/,
|
||||||
// TODO remove when plugins are built separately
|
include: cacheDir,
|
||||||
include: [cacheDir, ...pluginsPath],
|
oneOf: [
|
||||||
use: {
|
// Use babel-loader for files that distinct the ee and ce code
|
||||||
loader: require.resolve('babel-loader'),
|
// These files have an import Something from 'ee_else_ce/
|
||||||
options: {
|
{
|
||||||
cacheDirectory: true,
|
test(filePath) {
|
||||||
cacheCompression: isProduction,
|
if (!filePath) {
|
||||||
compact: isProduction,
|
return false;
|
||||||
presets: [
|
}
|
||||||
require.resolve('@babel/preset-env'),
|
|
||||||
require.resolve('@babel/preset-react'),
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
[
|
|
||||||
require.resolve('@strapi/babel-plugin-switch-ee-ce'),
|
|
||||||
{
|
|
||||||
// Imported this tells the custom plugin where to look for the ee folder
|
|
||||||
roots,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
require.resolve('@babel/plugin-proposal-class-properties'),
|
|
||||||
require.resolve('@babel/plugin-syntax-dynamic-import'),
|
|
||||||
require.resolve('@babel/plugin-transform-modules-commonjs'),
|
|
||||||
require.resolve('@babel/plugin-proposal-async-generator-functions'),
|
|
||||||
|
|
||||||
[
|
try {
|
||||||
require.resolve('@babel/plugin-transform-runtime'),
|
const fileContent = fse.readFileSync(filePath).toString();
|
||||||
{
|
|
||||||
// absoluteRuntime: true,s
|
if (fileContent.match(/from.* ['"]ee_else_ce\//)) {
|
||||||
helpers: true,
|
return true;
|
||||||
regenerator: true,
|
}
|
||||||
},
|
|
||||||
],
|
return false;
|
||||||
[require.resolve('babel-plugin-styled-components'), { pure: true }],
|
} catch (e) {
|
||||||
],
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
loader: require.resolve('babel-loader'),
|
||||||
|
options: {
|
||||||
|
cacheDirectory: true,
|
||||||
|
cacheCompression: isProduction,
|
||||||
|
compact: isProduction,
|
||||||
|
presets: [
|
||||||
|
require.resolve('@babel/preset-env'),
|
||||||
|
require.resolve('@babel/preset-react'),
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
require.resolve('@strapi/babel-plugin-switch-ee-ce'),
|
||||||
|
{
|
||||||
|
// Imported this tells the custom plugin where to look for the ee folder
|
||||||
|
roots,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
require.resolve('@babel/plugin-transform-runtime'),
|
||||||
|
{
|
||||||
|
helpers: true,
|
||||||
|
regenerator: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[require.resolve('babel-plugin-styled-components'), { pure: true }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Use esbuild-loader for the other files
|
||||||
|
{
|
||||||
|
use: {
|
||||||
|
loader: require.resolve('esbuild-loader'),
|
||||||
|
options: {
|
||||||
|
loader: 'jsx',
|
||||||
|
target: 'es2015',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.m?js$/,
|
||||||
|
include: pluginsPath,
|
||||||
|
use: {
|
||||||
|
loader: require.resolve('esbuild-loader'),
|
||||||
|
options: {
|
||||||
|
loader: 'jsx',
|
||||||
|
target: 'es2015',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -212,8 +236,6 @@ module.exports = ({
|
|||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: true,
|
inject: true,
|
||||||
template: path.resolve(__dirname, 'index.html'),
|
template: path.resolve(__dirname, 'index.html'),
|
||||||
// FIXME
|
|
||||||
// favicon: path.resolve(__dirname, 'admin/src/favicon.ico'),
|
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin(envVariables),
|
new webpack.DefinePlugin(envVariables),
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"main": "build/index.js",
|
"main": "build/index.js",
|
||||||
"module": "lib/src/index.js",
|
|
||||||
"files": [
|
"files": [
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
|
@ -20922,6 +20922,13 @@ specificity@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
|
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
|
||||||
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
|
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
|
||||||
|
|
||||||
|
speed-measure-webpack-plugin@1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz#caf2c5bee24ab66c1c7c30e8daa7910497f7681a"
|
||||||
|
integrity sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.1.0"
|
||||||
|
|
||||||
split-array-stream@^2.0.0:
|
split-array-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/split-array-stream/-/split-array-stream-2.0.0.tgz#85a4f8bfe14421d7bca7f33a6d176d0c076a53b1"
|
resolved "https://registry.yarnpkg.com/split-array-stream/-/split-array-stream-2.0.0.tgz#85a4f8bfe14421d7bca7f33a6d176d0c076a53b1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user