Use Vue CLI to build assets
|
|
@ -7,7 +7,7 @@ NINTENDO_SESSION_ID_JP=
|
|||
SPLATNET_USER_AGENT=
|
||||
|
||||
# (Optional) Google Analytics tracking ID
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
VUE_APP_GOOGLE_ANALYTICS_ID=
|
||||
|
||||
# (Optional) Sentry error reporting (https://sentry.io)
|
||||
SENTRY_DSN=
|
||||
|
|
|
|||
5
babel.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
],
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ server {
|
|||
add_header Content-Type text/calendar;
|
||||
}
|
||||
|
||||
# CORS (for third-party integrations)
|
||||
# CORS (for modern ES module support and third-party integrations)
|
||||
location /assets/ {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
|
|
|
|||
70
package.json
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "splatoon2.ink",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack",
|
||||
"watch": "npm run dev -- --watch",
|
||||
"build": "webpack -p --env production",
|
||||
"serve": "webpack-dev-server --hot --content-base dist/",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build --modern --no-clean",
|
||||
"lint": "vue-cli-service lint",
|
||||
"splatnet": "node src/app splatnet",
|
||||
"twitter": "node src/app twitter",
|
||||
"twitter:test": "node src/app twitterTest",
|
||||
|
|
@ -15,28 +15,15 @@
|
|||
"locale-man": "node node_modules/locale-man/ -l en,es,es-MX,fr,fr-CA,de,nl,it,ru,ja -o src/locale"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"axios": "^0.17.1",
|
||||
"babel-core": "^6.25.0",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"bulma": "^0.6.1",
|
||||
"bulma-tooltip": "^2.0.1",
|
||||
"clean-webpack-plugin": "^0.1.16",
|
||||
"console-stamp": "^0.2.5",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"cozy-ical": "^1.1.22",
|
||||
"cron": "^1.2.1",
|
||||
"css-loader": "^0.28.4",
|
||||
"delay": "^2.0.0",
|
||||
"dotenv": "^4.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^1.1.5",
|
||||
"he": "^1.1.1",
|
||||
"html-loader": "^0.5.0",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"jsonpath": "^1.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
|
|
@ -44,31 +31,54 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"module-alias": "^2.1.0",
|
||||
"moment-timezone": "^0.5.13",
|
||||
"node-sass": "^4.5.3",
|
||||
"postcss-loader": "^2.0.6",
|
||||
"puppeteer": "^0.13.0",
|
||||
"purify-css": "^1.2.5",
|
||||
"purifycss-webpack": "^0.7.0",
|
||||
"raven": "^2.1.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"style-loader": "^0.19.0",
|
||||
"twitter": "^1.7.1",
|
||||
"v-click-outside": "^1.0.0",
|
||||
"vue": "^2.4.2",
|
||||
"vue-clipboard2": "^0.2.0",
|
||||
"vue-loader": "^13.0.2",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.4.2",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-i18n": "^1.8.0",
|
||||
"webpack": "^3.4.1",
|
||||
"webpack-dev-server": "^2.6.1"
|
||||
"vuex-i18n": "^1.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.0.0-rc.9",
|
||||
"@vue/cli-plugin-eslint": "^3.0.0-rc.9",
|
||||
"@vue/cli-service": "^3.0.0-rc.9",
|
||||
"locale-man": "^0.0.5",
|
||||
"shipit-cli": "^3.0.0"
|
||||
"node-sass": "^4.9.2",
|
||||
"purify-css": "^1.2.5",
|
||||
"purifycss-webpack": "^0.7.0",
|
||||
"sass-loader": "^7.0.3",
|
||||
"shipit-cli": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.16"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@": "src"
|
||||
}
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"firefox esr",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer'),
|
||||
],
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -8,25 +8,25 @@
|
|||
<meta name="description" content="Splatoon 2 map rotation, upcoming Salmon Run schedules, SplatNet gear, and Splatfest info.">
|
||||
<meta property="og:title" content="Splatoon 2 Map Schedules">
|
||||
<meta property="og:description" content="Current/upcoming maps and Salmon Run schedules.">
|
||||
<meta property="og:image" content="https://splatoon2.ink/<%= require('../src/web/assets/icons/apple-touch-icon.png') %>">
|
||||
<meta property="og:image" content="https://splatoon2.ink/apple-touch-icon.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="Splatoon2.ink">
|
||||
<% for (var css in htmlWebpackPlugin.files.chunks.main.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.files.chunks.main.css[css] %>" rel="stylesheet">
|
||||
<% } %>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../src/web/assets/icons/apple-touch-icon.png') %>">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= require('../src/web/assets/icons/favicon-32x32.png') %>">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= require('../src/web/assets/icons/favicon-16x16.png') %>">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= BASE_URL %>apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= BASE_URL %>favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= BASE_URL %>favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
<link rel="mask-icon" href="<%= require('../src/web/assets/icons/safari-pinned-tab.svg') %>" color="#ff470f">
|
||||
<link rel="shortcut icon" href="<%= require('../src/web/assets/icons/favicon.ico') %>">
|
||||
<link rel="mask-icon" href="<%= BASE_URL %>safari-pinned-tab.svg" color="#ff470f">
|
||||
<link rel="shortcut icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<meta name="theme-color" content="#cccccc">
|
||||
<% if (process.env.VUE_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= VUE_APP_GOOGLE_ANALYTICS_ID %>"></script>
|
||||
<script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', '<%= VUE_APP_GOOGLE_ANALYTICS_ID %>');</script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
|
@ -4,12 +4,8 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<% for (var css in htmlWebpackPlugin.files.chunks.screenshots.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.files.chunks.screenshots.css[css] %>" rel="stylesheet">
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.screenshots.entry %>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
94
src/common/regions.esm.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// This is the same as regions.js but formatted for ES6 modules.
|
||||
// Temporary fix for importing with vue-cli until I find something that works with nodejs as well.
|
||||
|
||||
export const splatoonRegions = [
|
||||
{ key: null, name: 'Global', demonym: 'Global' },
|
||||
{ key: 'na', name: 'North America & Oceania', demonym: 'North American & Oceanian' },
|
||||
{ key: 'eu', name: 'Europe', demonym: 'European' },
|
||||
{ key: 'jp', name: 'Japan', demonym: 'Japanese' },
|
||||
];
|
||||
|
||||
export const languages = [
|
||||
{ region: 'NA', language: 'en', name: 'English' },
|
||||
{ region: 'EU', language: 'en', name: 'English (UK)' },
|
||||
{ region: 'EU', language: 'es', name: 'Español' },
|
||||
{ region: 'NA', language: 'es-MX', name: 'Español (MX)' },
|
||||
{ region: 'EU', language: 'fr', name: 'Français' },
|
||||
{ region: 'NA', language: 'fr-CA', name: 'Français (CA)' },
|
||||
{ region: 'EU', language: 'de', name: 'Deutsch' },
|
||||
{ region: 'EU', language: 'nl', name: 'Nederlands' },
|
||||
{ region: 'EU', language: 'it', name: 'Italiano' },
|
||||
{ region: 'EU', language: 'ru', name: 'Pусский' },
|
||||
{ region: 'JP', language: 'ja', name: '日本語' },
|
||||
];
|
||||
|
||||
export function getRegionByKey(key) {
|
||||
return splatoonRegions.find(r => r.key == key);
|
||||
}
|
||||
|
||||
export function detectSplatoonLanguage() {
|
||||
let browserLanguages = window.navigator.languages || [window.navigator.language];
|
||||
browserLanguages = browserLanguages.map(l => l.toLowerCase());
|
||||
let availableLanguages = languages.map(l => l.language.toLowerCase());
|
||||
|
||||
// Try to find a match based on the first part of the available languages (i.e., match "es" for "es-ES")
|
||||
for (let language of browserLanguages) {
|
||||
for (let availableLanguage of availableLanguages) {
|
||||
if (language.startsWith(availableLanguage))
|
||||
return availableLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function detectSplatoonRegion() {
|
||||
if (window.navigator && window.navigator.language)
|
||||
return detectSplatoonRegionFromLanguage(window.navigator.language);
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectSplatoonRegionFromLanguage(language) {
|
||||
switch (language) {
|
||||
case 'en-US': // USA
|
||||
case 'en-CA': // Canada (English)
|
||||
case 'fr-CA': // Canada (French)
|
||||
case 'es-MX': // Mexico
|
||||
case 'en-AU': // Australia
|
||||
case 'en-NZ': // New Zealand
|
||||
return 'na';
|
||||
|
||||
case 'de-AT': // Austria
|
||||
case 'nl-BE': // Belgium (Dutch)
|
||||
case 'fr-BE': // Belgium (French)
|
||||
case 'cs-CZ': // Czech Republic
|
||||
case 'da-DK': // Denmark
|
||||
case 'de': // Germany
|
||||
case 'de-DE': // Germany
|
||||
case 'es-ES': // Spain
|
||||
case 'fi-FI': // Finland
|
||||
case 'fr': // France
|
||||
case 'fr-FR': // France
|
||||
case 'el-GR': // Greece
|
||||
case 'hu-HU': // Hungary
|
||||
case 'it-IT': // Italy
|
||||
case 'nl': // Netherlands
|
||||
case 'nl-NL': // Netherlands
|
||||
case 'nb-NO': // Norway
|
||||
case 'pl-PL': // Poland
|
||||
case 'pt-PT': // Portugal
|
||||
case 'ru-RU': // Russia
|
||||
case 'en-ZA': // South Africa
|
||||
case 'sv-SE': // Sweden
|
||||
case 'de-CH': // Switzerland (German)
|
||||
case 'fr-CH': // Switzerland (French)
|
||||
case 'it-CH': // Switzerland (Italian)
|
||||
case 'en-GB': // United Kingdom
|
||||
case 'en-IE': // Ireland
|
||||
return 'eu';
|
||||
|
||||
case 'ja': // Japan
|
||||
case 'ja-JP': // Japan
|
||||
return 'jp';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -24,7 +24,8 @@ function getRegionByKey(key) {
|
|||
}
|
||||
|
||||
function detectSplatoonLanguage() {
|
||||
let browserLanguages = window.navigator.languages.map(l => l.toLowerCase());
|
||||
let browserLanguages = window.navigator.languages || [window.navigator.language];
|
||||
browserLanguages = browserLanguages.map(l => l.toLowerCase());
|
||||
let availableLanguages = languages.map(l => l.language.toLowerCase());
|
||||
|
||||
// Try to find a match based on the first part of the available languages (i.e., match "es" for "es-ES")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||
|
||||
if (GOOGLE_ANALYTICS_ID) {
|
||||
ga('create', GOOGLE_ANALYTICS_ID, 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
|
||||
export default {
|
||||
event() {
|
||||
ga('send', 'event', ...arguments);
|
||||
event(category, action, label = undefined) {
|
||||
if (window.gtag) {
|
||||
window.gtag('event', {
|
||||
event_category: category,
|
||||
event_action: action,
|
||||
event_label: label,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapActions } from 'vuex';
|
||||
import regions from '@/common/regions';
|
||||
import * as regions from '@/common/regions.esm';
|
||||
import Dropdown from './Dropdown.vue';
|
||||
import ScheduleBox from './splatoon/ScheduleBox.vue';
|
||||
import SalmonRunBox from './splatoon/SalmonRunBox.vue';
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import axios from 'axios';
|
|||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import SplatfestBox from '@/web/components/splatoon/SplatfestBox.vue';
|
||||
import SplatfestResultsBox from '@/web/components/splatoon/SplatfestResultsBox.vue';
|
||||
import { splatoonRegions } from '@/common/regions';
|
||||
import { splatoonRegions } from '@/common/regions.esm';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, SplatfestBox, SplatfestResultsBox },
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
<script>
|
||||
import Vue from 'vue';
|
||||
import analytics from '@/web/analytics';
|
||||
import regions from '@/common/regions';
|
||||
import * as regions from '@/common/regions.esm';
|
||||
import Modal from '@/web/components/Modal.vue';
|
||||
import Dropdown from '@/web/components/Dropdown.vue';
|
||||
import SplatfestBox from './SplatfestBox.vue';
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import './i18n';
|
|||
import './directives.js';
|
||||
import './filters.js';
|
||||
|
||||
import './assets/css/main.scss';
|
||||
|
||||
// Start the Vue app with our root component
|
||||
import App from './components/App.vue';
|
||||
new Vue({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import './i18n';
|
|||
import './directives.js';
|
||||
import './filters.js';
|
||||
|
||||
import './assets/css/screenshots.scss';
|
||||
|
||||
import VueRouter from 'vue-router';
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
loadLocale({ dispatch }, locale) {
|
||||
import(/* webpackChunkName: "lang-[request]" */ `@/web/locale/${locale}`)
|
||||
.then(translations => dispatch('addLocale', { locale, translations }));
|
||||
.then(translations => dispatch('addLocale', { locale, translations: translations.default }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
vue.config.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
|
||||
const PurifyCSSPlugin = require('purifycss-webpack');
|
||||
|
||||
module.exports = {
|
||||
assetsDir: 'assets',
|
||||
productionSourceMap: false,
|
||||
configureWebpack: {
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
// We want to keep the vendors chunk, but we don't want to include moment since it's relatively large
|
||||
// and only used for the screenshots page (which is never served to the client).
|
||||
// An alternative method would be to set minChunks to 2 here.
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/](?!(moment|moment-timezone)[\\/])/
|
||||
},
|
||||
// The common chunk doesn't really make sense here since the only reason we're splitting
|
||||
// pages is for the server-side screenshots page. Disabling it to save an HTTP request.
|
||||
common: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new PurifyCSSPlugin({
|
||||
paths: [
|
||||
...glob.sync(path.join(__dirname, 'src/web/html/**/*.html')),
|
||||
...glob.sync(path.join(__dirname, 'src/web/components/**/*.vue')),
|
||||
],
|
||||
purifyOptions: {
|
||||
whitelist: [
|
||||
'.title:not(.is-spaced)+.subtitle', // Fix subtitle spacing
|
||||
// Dynamic merchandise types
|
||||
'.merchandise-box.shoes',
|
||||
'.merchandise-box.head',
|
||||
'.merchandise-box.clothes',
|
||||
],
|
||||
cleanCssOptions: {
|
||||
rebase: false, // Leave relative paths alone when minifying CSS
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
chainWebpack: config => {
|
||||
config.plugin('prefetch-index').tap(options => {
|
||||
options[0].fileBlacklist = options[0].fileBlacklist || [];
|
||||
options[0].fileBlacklist.push(/lang-.+?\.js$/);
|
||||
return options;
|
||||
});
|
||||
},
|
||||
devServer: {
|
||||
contentBase: [
|
||||
'./public',
|
||||
'./dist'
|
||||
]
|
||||
},
|
||||
pages: {
|
||||
index: {
|
||||
entry: 'src/web/main.js'
|
||||
},
|
||||
screenshots: {
|
||||
entry: 'src/web/screenshots.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
require('dotenv').config();
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const PurifyCSSPlugin = require('purifycss-webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = function(env) {
|
||||
const production = (env === 'production');
|
||||
|
||||
return {
|
||||
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
|
||||
entry: {
|
||||
main: [
|
||||
'./src/web/main.js',
|
||||
'./src/web/assets/css/main.scss',
|
||||
],
|
||||
screenshots: [
|
||||
'./src/web/screenshots.js',
|
||||
'./src/web/assets/css/screenshots.scss',
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
filename: 'assets/js/[name].[hash:6].js',
|
||||
chunkFilename: 'assets/js/[name].[hash:6].js',
|
||||
},
|
||||
devtool: (production) ? false : '#cheap-module-eval-source-map',
|
||||
devServer: {
|
||||
overlay: true,
|
||||
historyApiFallback: true,
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
extractCSS: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
publicPath: '../../',
|
||||
use: [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { sourceMap: !production },
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: { sourceMap: !production },
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: { sourceMap: !production },
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|ttf|eot)$/,
|
||||
loader: 'file-loader?name=assets/fonts/[name].[hash:6].[ext]',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
loader: 'file-loader?name=assets/img/[name].[hash:6].[ext]',
|
||||
},
|
||||
{
|
||||
test: /favicon\.ico$/,
|
||||
loader: 'file-loader?name=favicon.ico',
|
||||
}
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Remove old files
|
||||
// new CleanWebpackPlugin([
|
||||
// 'dist/assets/css/*',
|
||||
// 'dist/assets/js/*',
|
||||
// ]),
|
||||
new webpack.DefinePlugin({
|
||||
'GOOGLE_ANALYTICS_ID': JSON.stringify(process.env.GOOGLE_ANALYTICS_ID),
|
||||
}),
|
||||
// Extract CSS to a separate file
|
||||
new ExtractTextPlugin({
|
||||
filename: 'assets/css/[name].[contenthash:6].css',
|
||||
disable: !production,
|
||||
}),
|
||||
// Remove unused CSS styles
|
||||
new PurifyCSSPlugin({
|
||||
paths: [
|
||||
...glob.sync(path.join(__dirname, 'public/**/*.html')),
|
||||
...glob.sync(path.join(__dirname, 'src/web/components/**/*.vue')),
|
||||
],
|
||||
minimize: production,
|
||||
purifyOptions: {
|
||||
whitelist: [
|
||||
'.title:not(.is-spaced)+.subtitle', // Fix subtitle spacing
|
||||
// Dynamic merchandise types
|
||||
'.merchandise-box.shoes',
|
||||
'.merchandise-box.head',
|
||||
'.merchandise-box.clothes',
|
||||
],
|
||||
cleanCssOptions: {
|
||||
rebase: false, // Leave relative paths alone when minifying CSS
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Build main public HTML
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
filename: 'index.html',
|
||||
template: 'public/index.html',
|
||||
minify: { collapseWhitespace: true },
|
||||
}),
|
||||
// Build HTML used for screenshot generation
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
filename: 'screenshots.html',
|
||||
template: 'public/screenshots.html',
|
||||
minify: { collapseWhitespace: true },
|
||||
}),
|
||||
// Copy additional favicons
|
||||
new CopyWebpackPlugin([
|
||||
{ from: 'public' },
|
||||
]),
|
||||
],
|
||||
}
|
||||
}
|
||||