add localisation via ttag

This commit is contained in:
HF 2021-01-29 02:15:33 +01:00
parent 087319be96
commit 0c5ab8bf97
17 changed files with 1454 additions and 207 deletions

13
i18n/de.po Normal file
View File

@ -0,0 +1,13 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals = 2; plural = (n != 1);\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/components/ForgotPasswordModal.jsx:20
#: src/components/RegisterModal.jsx:21
#: src/components/UserAreaModal.jsx:128
msgid "Consider joining us on Guilded:"
msgstr "Sprich zu uns und anderen Spielern auf guilded:"

13
i18n/es.po Normal file
View File

@ -0,0 +1,13 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals = 2; plural = (n != 1);\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/components/ForgotPasswordModal.jsx:20
#: src/components/RegisterModal.jsx:21
#: src/components/UserAreaModal.jsx:128
msgid "Consider joining us on Guilded:"
msgstr "Sprich zu uns und anderen Spielern auf guilded:"

919
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -77,6 +77,7 @@
"sweetalert2": "^10.12.1", "sweetalert2": "^10.12.1",
"three": "^0.123.0", "three": "^0.123.0",
"three-trackballcontrols": "^0.9.0", "three-trackballcontrols": "^0.9.0",
"ttag": "^1.7.24",
"url-search-params-polyfill": "^8.1.0", "url-search-params-polyfill": "^8.1.0",
"winston": "^3.3.3", "winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0", "winston-daily-rotate-file": "^4.5.0",
@ -115,6 +116,7 @@
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"babel-plugin-transform-react-pure-class-to-function": "^1.0.1", "babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24", "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-plugin-ttag": "^1.7.30",
"clean-css": "^4.2.3", "clean-css": "^4.2.3",
"css-loader": "^5.0.1", "css-loader": "^5.0.1",
"eslint": "^7.15.0", "eslint": "^7.15.0",
@ -134,6 +136,7 @@
"react-svg-loader": "^3.0.3", "react-svg-loader": "^3.0.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"ttag-cli": "^1.9.1",
"webpack": "^5.10.0", "webpack": "^5.10.0",
"webpack-bundle-analyzer": "^4.2.0", "webpack-bundle-analyzer": "^4.2.0",
"webpack-cli": "^4.2.0", "webpack-cli": "^4.2.0",

View File

@ -7,27 +7,31 @@
* LICENSE.txt file in the root directory of this source tree. * LICENSE.txt file in the root directory of this source tree.
*/ */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import fs from 'fs'; import fs from 'fs';
import path from 'path';
import webpack from 'webpack'; import webpack from 'webpack';
import webpackConfigWeb from '../webpack.config.web.babel'; import webpackConfigWeb from '../webpack.config.web.babel';
import webpackConfigClient from '../webpack.config.client.babel'; import { buildWebpackClientConfig } from '../webpack.config.client.babel';
const wpStats = { const wpStats = {
colors: true, colors: true,
reasons: false, reasons: false,
hash: false, hash: false,
version: false, version: false,
timings: true, timings: true,
chunks: true, chunks: true,
chunkModules: false, chunkModules: false,
cached: false, cached: false,
cachedAssets: false, cachedAssets: false,
} };
/** /**
* Creates application bundles from the source files. * Creates application bundles from the source files.
*/ */
function bundle() { async function bundle() {
try { try {
/* fix image-q imports here /* fix image-q imports here
* Pretty dirty, but we did write an issue and they might * Pretty dirty, but we did write an issue and they might
@ -36,12 +40,21 @@ function bundle() {
console.log('Patching image-q set-immediate import'); console.log('Patching image-q set-immediate import');
const regex = /core-js\/fn\/set-immediate/g; const regex = /core-js\/fn\/set-immediate/g;
const files = [ const files = [
'./node_modules/image-q/dist/esm/basicAPI.js', path.resolve(
'./node_modules/image-q/dist/esm/helper.js', '..', 'node_modules',
'image-q', 'dist', 'esm', 'basicAPI.js',
),
path.resolve(
'..', 'node_modules',
'image-q', 'dist', 'esm', 'helper.js',
),
]; ];
files.forEach((file) => { files.forEach((file) => {
let fileContent = fs.readFileSync(file,'utf8'); let fileContent = fs.readFileSync(file, 'utf8');
fileContent = fileContent.replace(regex, 'core-js/features/set-immediate'); fileContent = fileContent.replace(
regex,
'core-js/features/set-immediate',
);
fs.writeFileSync(file, fileContent); fs.writeFileSync(file, fileContent);
}); });
console.log('Patching image-q done'); console.log('Patching image-q done');
@ -50,8 +63,24 @@ function bundle() {
} }
console.log('Bundle with webpack....'); console.log('Bundle with webpack....');
let webpackConfig = [
webpackConfigWeb,
buildWebpackClientConfig(false, false, 'default'),
]
/*
* add other language configs
*/
const langDir = path.resolve(__dirname, '..', 'i18n');
const langs = fs.readdirSync(langDir)
.filter((e) => e.endsWith('.po'))
.map((l) => l.slice(0, -3));
webpackConfig = webpackConfig.concat(
langs.map((l) => buildWebpackClientConfig(false, false, l)),
);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
webpack([webpackConfigWeb, webpackConfigClient]).run((err, stats) => { webpack(webpackConfig).run((err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }

View File

@ -5,7 +5,7 @@
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import path from 'path'; import path from 'path';
const builddir = path.resolve(__dirname, '../build'); const builddir = path.resolve(__dirname, '..', 'build');
function clean() { function clean() {

View File

@ -9,10 +9,10 @@ import path from 'path';
import glob from 'glob'; import glob from 'glob';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
const builddir = path.resolve(__dirname, '../build'); const builddir = path.resolve(__dirname, '..', 'build');
const deploydir = path.resolve(__dirname, '../deployment'); const deploydir = path.resolve(__dirname, '..', 'deployment');
const publicdir = path.resolve(__dirname, '../public'); const publicdir = path.resolve(__dirname, '..', 'public');
const srcdir = path.resolve(__dirname, '../src'); const srcdir = path.resolve(__dirname, '..', 'src');
/* /*
* following functions are copied from * following functions are copied from

View File

@ -9,10 +9,10 @@ import CleanCSS from 'clean-css';
import crypto from 'crypto'; import crypto from 'crypto';
const rootdir = path.resolve(__dirname, '..'); const rootdir = path.resolve(__dirname, '..');
const assetdir = path.resolve(__dirname, '../build/public/assets'); const assetdir = path.resolve(__dirname, '..', 'build', 'public', 'assets');
const builddir = path.resolve(__dirname, '../build'); const builddir = path.resolve(__dirname, '..', 'build');
const FOLDER = path.resolve(__dirname, '../src/styles'); const FOLDER = path.resolve(__dirname, '..', 'src', 'styles');
const FILES = [ const FILES = [
'default.css', 'default.css',
'dark.css', 'dark.css',

View File

@ -5,6 +5,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { t } from 'ttag';
import { showUserAreaModal } from '../actions'; import { showUserAreaModal } from '../actions';
import NewPasswordForm from './NewPasswordForm'; import NewPasswordForm from './NewPasswordForm';
@ -16,7 +17,7 @@ const ForgotPasswordModal = ({ login }) => (
</p><br /> </p><br />
<p style={{ textAlign: 'center' }}> <p style={{ textAlign: 'center' }}>
<NewPasswordForm back={login} /> <NewPasswordForm back={login} />
<p>Consider joining us on Guilded:&nbsp; <p>{t`Consider joining us on Guilded:`}&nbsp;
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a> <a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
</p> </p>
</p> </p>

View File

@ -29,20 +29,33 @@ const styles = [{
}]; }];
const title = 'PixelPlanet.fun 3DGlobe'; const title = 'PixelPlanet.fun 3DGlobe';
const description = '3D globe of our canvas'; const description = 'pixelplanet globe';
const scripts = [ const defaultScripts = assets.globe.js.map(
ASSET_SERVER + assets.three.js, (s) => ASSET_SERVER + s,
ASSET_SERVER + assets.globe.js, );
];
const body = <Globe />; const body = <Globe />;
const globeHtml = `<!doctype html>${ReactDOM.renderToStaticMarkup(
<Html
title={title}
description={description}
scripts={scripts}
body={body}
styles={styles}
/>,
)}`;
export default globeHtml; /*
* generates string with html of globe page
* @param lang language code
* @return html of mainpage
*/
function generateGlobePage(lang: string): string {
const scripts = (assets[`globe-${lang}`])
? assets[`globe-${lang}`].js.map((s) => ASSET_SERVER + s)
: defaultScripts;
const html = ReactDOM.renderToStaticMarkup(
<Html
title={title}
description={description}
scripts={scripts}
body={body}
styles={styles}
/>,
);
return `<!doctype html>${html}`;
}
export default generateGlobePage;

View File

@ -22,10 +22,9 @@ let code = `window.assetserver="${ASSET_SERVER}";window.availableStyles=JSON.par
if (BACKUP_URL) { if (BACKUP_URL) {
code += `window.backupurl="${BACKUP_URL}";`; code += `window.backupurl="${BACKUP_URL}";`;
} }
const scripts = [ const defaultScripts = assets.client.js.map(
ASSET_SERVER + assets.vendor.js, (s) => ASSET_SERVER + s,
ASSET_SERVER + assets.client.js, );
];
const css = [ const css = [
{ {
id: 'globcss', id: 'globcss',
@ -43,6 +42,9 @@ const css = [
*/ */
function generateMainPage(countryCoords: Cell, lang: string): string { function generateMainPage(countryCoords: Cell, lang: string): string {
const [x, y] = countryCoords; const [x, y] = countryCoords;
const scripts = (assets[`client-${lang}`])
? assets[`client-${lang}`].js.map((s) => ASSET_SERVER + s)
: defaultScripts;
// eslint-disable-next-line // eslint-disable-next-line
const html = ReactDOM.renderToStaticMarkup( const html = ReactDOM.renderToStaticMarkup(
<Html <Html

View File

@ -5,6 +5,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { t } from 'ttag';
import { showUserAreaModal } from '../actions'; import { showUserAreaModal } from '../actions';
@ -17,7 +18,7 @@ const RegisterModal = ({ login }) => (
<p className="modaltext">Register new account here</p><br /> <p className="modaltext">Register new account here</p><br />
<p style={{ textAlign: 'center' }}> <p style={{ textAlign: 'center' }}>
<SignUpForm back={login} /> <SignUpForm back={login} />
<p>Consider joining us on Guilded:&nbsp; <p>{t`Consider joining us on Guilded:`}&nbsp;
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a> <a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
</p> </p>
</p> </p>

View File

@ -5,6 +5,7 @@
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { t } from 'ttag';
import type { State } from '../reducers'; import type { State } from '../reducers';
@ -124,7 +125,7 @@ const UserAreaModal = ({
)} )}
</Tabs> </Tabs>
)} )}
<p>Consider joining us on Guilded:&nbsp; <p>{t`Consider joining us on Guilded:`}&nbsp;
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a> <a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
</p> </p>
</p> </p>

View File

@ -26,7 +26,7 @@ import {
admintools, admintools,
resetPassword, resetPassword,
} from './routes'; } from './routes';
import globeHtml from './components/Globe'; import generateGlobePage from './components/Globe';
import generateMainPage from './components/Main'; import generateMainPage from './components/Main';
import { SECOND, MONTH } from './core/constants'; import { SECOND, MONTH } from './core/constants';
@ -142,7 +142,7 @@ app.use('/reset_password', resetPassword);
// 3D Globe (react generated) // 3D Globe (react generated)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const globeEtag = etag( const globeEtag = etag(
`${assets.globe.js}`, assets.globe.js.join('_'),
{ weak: true }, { weak: true },
); );
app.get('/globe', async (req, res) => { app.get('/globe', async (req, res) => {
@ -157,7 +157,10 @@ app.get('/globe', async (req, res) => {
return; return;
} }
res.status(200).send(globeHtml); const language = req.headers['accept-language'];
const lang = (language) ? languageFromLocalisation(language) : 'en';
res.status(200).send(generateGlobePage(lang));
}); });
@ -165,7 +168,7 @@ app.get('/globe', async (req, res) => {
// Main Page (react generated) // Main Page (react generated)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const indexEtag = etag( const indexEtag = etag(
`${assets.vendor.js},${assets.client.js}`, assets.client.js.join('_'),
{ weak: true }, { weak: true },
); );
@ -183,7 +186,7 @@ app.get('/', async (req, res) => {
// get start coordinates based on cloudflare header country // get start coordinates based on cloudflare header country
const country = req.headers['cf-ipcountry']; const country = req.headers['cf-ipcountry'];
let language = req.headers['accept-language']; const language = req.headers['accept-language'];
const lang = (language) ? languageFromLocalisation(language) : 'en'; const lang = (language) ? languageFromLocalisation(language) : 'en';
const countryCoords = (country) ? ccToCoords(country) : [0, 0]; const countryCoords = (country) ? ccToCoords(country) : [0, 0];

View File

@ -8,166 +8,185 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import pkg from './package.json'; import pkg from './package.json';
const isDebug = process.argv.includes('--debug'); /*
const VERBOSE = false; * Emit a file with assets paths
const isAnalyze = process.argv.includes('--analyze') */
|| process.argv.includes('--analyse'); const assetPlugin = new AssetsPlugin({
path: path.resolve(__dirname, 'build'),
filename: 'assets.json',
entrypoints: true,
prettyPrint: true,
});
export function buildWebpackClientConfig(development, analyze, locale) {
const ttag = {
resolve: {
translations: (locale !== 'default')
? path.resolve(__dirname, 'i18n', `${locale}.po`)
: locale,
},
};
const babelPlugins = [ const babelPlugins = [
'@babel/transform-flow-strip-types', '@babel/transform-flow-strip-types',
['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-function-sent', '@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-export-namespace-from', '@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-numeric-separator', '@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions', '@babel/plugin-proposal-throw-expressions',
['@babel/plugin-proposal-class-properties', { loose: true }], ['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/proposal-object-rest-spread', '@babel/proposal-object-rest-spread',
// react-optimize // react-optimize
'@babel/transform-react-constant-elements', '@babel/transform-react-constant-elements',
'@babel/transform-react-inline-elements', '@babel/transform-react-inline-elements',
'transform-react-remove-prop-types', 'transform-react-remove-prop-types',
'transform-react-pure-class-to-function', 'transform-react-pure-class-to-function',
]; ['ttag', ttag],
];
return {
name: 'client',
target: 'web',
export default { context: __dirname,
name: 'client', mode: (development) ? 'development' : 'production',
target: 'web', devtool: 'source-map',
context: __dirname, entry: {
mode: (isDebug) ? 'development' : 'production', [(locale !== 'default') ? `client-${locale}` : 'client']:
devtool: 'source-map', ['./src/client.js'],
[(locale !== 'default') ? `globe-${locale}` : 'globe']:
['./src/globe.js'],
},
entry: { output: {
client: ['./src/client.js'], path: path.resolve(__dirname, 'build', 'public', 'assets'),
globe: ['./src/globe.js'], publicPath: '/assets/',
}, filename: '[name].[chunkhash:8].js',
chunkFilename: (locale !== 'default')
? `[name]-${locale}.[chunkhash:8].js`
: '[name].[chunkhash:8].js',
},
output: { resolve: {
path: path.resolve(__dirname, './build/public/assets'), alias: {
publicPath: '/assets/', ttag: 'ttag/dist/mock',
pathinfo: VERBOSE,
filename: isDebug ? '[name].js' : '[name].[chunkhash:8].js',
chunkFilename: isDebug ? '[name].chunk.js' : '[name].[chunkhash:8].js',
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: 'babel-loader',
},
{
loader: 'react-svg-loader',
options: {
svgo: {
plugins: [
{
removeViewBox: false,
},
{
removeDimensions: true,
},
],
},
jsx: false,
},
},
],
}, },
{ extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
test: /\.(js|jsx|ts|tsx)$/, },
loader: 'babel-loader',
include: [ module: {
path.resolve(__dirname, './src'), rules: [
], {
options: { test: /\.svg$/,
cacheDirectory: isDebug, use: [
babelrc: false, {
presets: [ loader: 'babel-loader',
['@babel/preset-env', { },
targets: { {
browsers: pkg.browserslist, loader: 'react-svg-loader',
options: {
svgo: {
plugins: [
{
removeViewBox: false,
},
{
removeDimensions: true,
},
],
},
jsx: false,
}, },
modules: false, },
useBuiltIns: 'usage',
corejs: {
version: 3,
},
debug: false,
}],
'@babel/typescript',
'@babel/react',
], ],
plugins: babelPlugins,
}, },
}, {
{ test: /\.(js|jsx|ts|tsx)$/,
test: /\.css/, loader: 'babel-loader',
use: ['style-loader', include: [
{ path.resolve(__dirname, 'src'),
loader: 'css-loader', ],
options: { options: {
sourceMap: isDebug, cacheDirectory: development,
modules: false, babelrc: false,
}, presets: [
['@babel/preset-env', {
targets: {
browsers: pkg.browserslist,
},
modules: false,
useBuiltIns: 'usage',
corejs: {
version: 3,
},
debug: false,
}],
'@babel/typescript',
'@babel/react',
],
plugins: babelPlugins,
}, },
],
},
],
},
plugins: [
// Define free variables
// https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': isDebug ? '"development"' : '"production"',
'process.env.BROWSER': true,
}),
// Emit a file with assets paths
// https://github.com/sporto/assets-webpack-plugin#options
new AssetsPlugin({
path: path.resolve(__dirname, './build'),
filename: 'assets.json',
prettyPrint: true,
}),
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...isAnalyze ? [new BundleAnalyzerPlugin()] : [],
],
optimization: {
splitChunks: {
chunks: 'all',
name: false,
cacheGroups: {
default: false,
defaultVendors: false,
vendor: {
name: 'vendor',
chunks: (chunk) => chunk.name === 'client',
test: /node_modules/,
}, },
three: { {
test: /[\\/]node_modules[\\/]three[\\/]/, test: /\.css/,
name: 'three', use: ['style-loader',
chunks: 'all', {
loader: 'css-loader',
options: {
sourceMap: development,
modules: false,
},
},
],
},
],
},
plugins: [
// Define free variables
// https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': development ? '"development"' : '"production"',
'process.env.BROWSER': true,
}),
assetPlugin,
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...analyze ? [new BundleAnalyzerPlugin({ analyzerPort: 8889 })] : [],
],
optimization: {
splitChunks: {
chunks: 'all',
name: false,
cacheGroups: {
default: false,
defaultVendors: false,
vendor: {
name: 'vendor',
chunks: (chunk) => chunk.name.startsWith('client'),
test: /[\\/]node_modules[\\/]/,
},
three: {
name: 'three',
chunks: 'all',
test: /[\\/]node_modules[\\/]three[\\/]/,
},
}, },
}, },
}, },
},
bail: !isDebug, cache: true,
};
}
export default buildWebpackClientConfig(
process.argv.includes('--debug'),
process.argv.includes('--analyse') || process.argv.includes('--analyze'),
'default',
);
cache: isDebug,
};

View File

@ -0,0 +1,237 @@
/**
*/
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import AssetsPlugin from 'assets-webpack-plugin';
import TtagWebpackPlugin from 'ttag-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import pkg from './package.json';
const isDebug = process.argv.includes('--debug');
const VERBOSE = false;
const isAnalyze = process.argv.includes('--analyze')
|| process.argv.includes('--analyse');
// activate for deprecation tracking
// process.traceDeprecation = true;
const babelPlugins = [
'@babel/transform-flow-strip-types',
['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions',
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/proposal-object-rest-spread',
// react-optimize
'@babel/transform-react-constant-elements',
'@babel/transform-react-inline-elements',
'transform-react-remove-prop-types',
'transform-react-pure-class-to-function',
];
/*
* get all available languages
*/
const langDir = path.resolve(__dirname, 'i18n');
const langs = fs.readdirSync(langDir)
.filter((e) => e.endsWith('.po'))
.map((l) => l.slice(0, -3));
/*
* create list of translations for ttag-webpack-plugin
*/
const translations = {};
for (let i = 0; i < langs.length; i += 1) {
const lang = langs[i];
translations[lang] = path.resolve(langDir, `${lang}.po`);
}
/*
* Cache Groups for splitChunks
* (edit cache groups here, make sure that chunks is based on name)
*/
const cacheGroups = {
default: false,
defaultVendors: false,
vendor: {
name: 'vendor',
chunks: (chunk) => chunk.name === 'client',
test: /[\\/]node_modules[\\/]/,
},
three: {
name: 'three',
chunks: (chunk) => (chunk.name === 'globe' || chunk.name === 'voxel'),
test: /[\\/]node_modules[\\/]three[\\/]/,
},
};
/*
* automatically add Cache Groups for languages based on
* manually set cacheGroups
*/
const groups = Object.keys(cacheGroups);
for (let u = 0; u < groups.length; u += 1) {
const group = cacheGroups[groups[u]];
if (!group.test) continue;
for (let i = 0; i < langs.length; i += 1) {
const lang = langs[i];
/* add lang */
const key = `${groups[u]}-${lang}`;
const name = `${group.name}-${lang}`;
const { test, chunks } = group;
cacheGroups[key] = {
name,
chunks: (chunk) => {
if (!chunk.name.endsWith(`-${lang}`)) {
return false;
}
return chunks({
name: chunk.name.slice(0, -lang.length - 1),
});
},
test,
};
}
}
export default {
name: 'client',
target: 'web',
context: __dirname,
mode: (isDebug) ? 'development' : 'production',
devtool: 'source-map',
entry: {
client: ['./src/client.js'],
globe: ['./src/globe.js'],
},
output: {
path: path.resolve(__dirname, 'build', 'public', 'assets'),
publicPath: '/assets/',
pathinfo: VERBOSE,
filename: isDebug ? '[name].js' : '[name].[chunkhash:8].js',
chunkFilename: isDebug ? '[name].chunk.js' : '[name].[chunkhash:8].js',
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: 'babel-loader',
},
{
loader: 'react-svg-loader',
options: {
svgo: {
plugins: [
{
removeViewBox: false,
},
{
removeDimensions: true,
},
],
},
jsx: false,
},
},
],
},
{
test: /\.(js|jsx|ts|tsx)$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, 'src'),
],
options: {
cacheDirectory: isDebug,
babelrc: false,
presets: [
['@babel/preset-env', {
targets: {
browsers: pkg.browserslist,
},
modules: false,
useBuiltIns: 'usage',
corejs: {
version: 3,
},
debug: false,
}],
'@babel/typescript',
'@babel/react',
],
plugins: babelPlugins,
},
},
{
test: /\.css/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
sourceMap: isDebug,
modules: false,
},
},
],
},
],
},
plugins: [
// Define free variables
// https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': isDebug ? '"development"' : '"production"',
'process.env.BROWSER': true,
}),
// make multiple bundles for each language
new TtagWebpackPlugin({
translations,
}),
// Emit a file with assets paths
// https://github.com/sporto/assets-webpack-plugin#options
new AssetsPlugin({
path: path.resolve(__dirname, 'build'),
filename: 'assets.json',
prettyPrint: true,
}),
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...isAnalyze ? [new BundleAnalyzerPlugin()] : [],
],
optimization: {
splitChunks: {
chunks: 'all',
name: false,
cacheGroups,
},
},
bail: !isDebug,
cache: isDebug,
};

View File

@ -9,7 +9,6 @@ import GeneratePackageJsonPlugin from 'generate-package-json-webpack-plugin';
import pkg from './package.json'; import pkg from './package.json';
const isDebug = process.argv.includes('--debug'); const isDebug = process.argv.includes('--debug');
const VERBOSE = false;
const basePackageValues = { const basePackageValues = {
name: pkg.name, name: pkg.name,
@ -54,8 +53,7 @@ export default {
}, },
output: { output: {
pathinfo: VERBOSE, path: path.resolve(__dirname, 'build'),
path: path.resolve(__dirname, './build'),
libraryTarget: 'commonjs2', libraryTarget: 'commonjs2',
}, },
@ -69,7 +67,7 @@ export default {
test: /\.(js|jsx|ts|tsx)$/, test: /\.(js|jsx|ts|tsx)$/,
loader: 'babel-loader', loader: 'babel-loader',
include: [ include: [
path.resolve(__dirname, './src'), path.resolve(__dirname, 'src'),
], ],
options: { options: {
cacheDirectory: isDebug, cacheDirectory: isDebug,
@ -121,9 +119,8 @@ export default {
}), }),
// create package.json for deployment // create package.json for deployment
new GeneratePackageJsonPlugin(basePackageValues, { new GeneratePackageJsonPlugin(basePackageValues, {
debug: VERBOSE,
sourcePackageFilenames: [ sourcePackageFilenames: [
path.resolve(__dirname, './package.json'), path.resolve(__dirname, 'package.json'),
], ],
}), }),
], ],
@ -133,8 +130,4 @@ export default {
__filename: false, __filename: false,
__dirname: false, __dirname: false,
}, },
bail: !isDebug,
cache: isDebug,
}; };