Switch the site to Astro
This commit is contained in:
parent
a5f64a3ff8
commit
2e02e52661
14 changed files with 8633 additions and 17018 deletions
|
|
@ -1 +0,0 @@
|
||||||
**/*.js
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2020": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 11,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": ["unused-imports", "@typescript-eslint", "prettier"],
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": "error",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"unused-imports/no-unused-imports-ts": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
target
|
.astro
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
12
.prettierrc
12
.prettierrc
|
|
@ -4,7 +4,13 @@
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"endOfLine": "lf",
|
"endOfLine": "lf",
|
||||||
"importOrder": ["^[./]", ".*", ".scss$"],
|
"plugins": ["prettier-plugin-astro"],
|
||||||
"importOrderSeparation": true,
|
"overrides": [
|
||||||
"importOrderSortSpecifiers": true
|
{
|
||||||
|
"files": "*.astro",
|
||||||
|
"options": {
|
||||||
|
"parser": "astro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -37,6 +37,6 @@
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"node_modules": true
|
"node_modules": true
|
||||||
},
|
},
|
||||||
"editor.rulers": [120],
|
"editor.rulers": [90],
|
||||||
"editor.wordWrap": "on"
|
"editor.wordWrap": "on"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
|
@ -2,7 +2,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Format and lint",
|
"label": "Lint",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "npm run lint",
|
"command": "npm run lint",
|
||||||
"group": "test",
|
"group": "test",
|
||||||
|
|
|
||||||
40
README.md
40
README.md
|
|
@ -1,21 +1,35 @@
|
||||||
# Portfolio
|
# schmelczer.dev
|
||||||
|
|
||||||
> An easy-to-configure timeline for your projects.
|
A static personal blog for Andras Schmelczer, built with Astro.
|
||||||
|
|
||||||
[Check out the live version.](https://schmelczer.dev)
|
The site is article-first: articles live in `src/content/posts`, project index entries
|
||||||
|
live in `src/content/projects`, and normal pages are rendered as static HTML with no
|
||||||
|
required client JavaScript.
|
||||||
|
|
||||||
## Configuration
|
## Setup
|
||||||
|
|
||||||
- The actual content is in the [data](src/data) folder, starting with [portfolio.ts](src/data/portfolio.ts)
|
```sh
|
||||||
- The assets referenced should be located in [data/media](src/data/media)
|
npm ci
|
||||||
|
npx playwright install --with-deps chromium # required before `npm run qa:overflow`
|
||||||
|
```
|
||||||
|
|
||||||
## Build
|
## Commands
|
||||||
|
|
||||||
1. `npm install`
|
```sh
|
||||||
2. `npm run build`
|
npm run dev
|
||||||
3. You can find the results in the [dist](dist) folder
|
npm run lint
|
||||||
|
npm run build
|
||||||
|
npm run preview
|
||||||
|
npm run qa
|
||||||
|
```
|
||||||
|
|
||||||
## Info
|
## Structure
|
||||||
|
|
||||||
- All images are converted to `WebP` after being imported into any file.
|
- `src/content/posts`: Markdown articles
|
||||||
> Except for the og-image, and SVGs.
|
- `src/content/projects`: project index entries
|
||||||
|
- `src/pages`: static routes
|
||||||
|
- `src/layouts`: page and post layouts
|
||||||
|
- `src/components`: reusable UI pieces
|
||||||
|
- `src/styles/global.css`: the visual system
|
||||||
|
- `public/media/downloads`: CV and thesis PDFs
|
||||||
|
- `public/media/video`: project videos
|
||||||
|
|
|
||||||
136
astro.config.mjs
Normal file
136
astro.config.mjs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import sitemap from '@astrojs/sitemap';
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||||
|
import rehypeSlug from 'rehype-slug';
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
// Build a lookup of post slugs to their last modification dates so the sitemap
|
||||||
|
// can advertise accurate <lastmod> values to crawlers. astro:content isn't
|
||||||
|
// available inside the config, so we read post frontmatter directly. Our posts
|
||||||
|
// always use single-line scalar `date:` / `updated:` keys, so a small regex
|
||||||
|
// extraction is sufficient and intentional.
|
||||||
|
const postsDir = path.resolve(
|
||||||
|
path.dirname(fileURLToPath(import.meta.url)),
|
||||||
|
'src/content/posts'
|
||||||
|
);
|
||||||
|
|
||||||
|
function extractScalar(frontmatter, key) {
|
||||||
|
const match = frontmatter.match(new RegExp(`^${key}:\\s*(.+?)\\s*$`, 'm'));
|
||||||
|
return match?.[1]?.replace(/^['"]|['"]$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const postLastmodLookup = new Map(
|
||||||
|
readdirSync(postsDir, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
||||||
|
.map((entry) => {
|
||||||
|
const raw = readFileSync(path.join(postsDir, entry.name), 'utf8');
|
||||||
|
const frontmatter = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '';
|
||||||
|
const rawDate =
|
||||||
|
extractScalar(frontmatter, 'updated') ?? extractScalar(frontmatter, 'date');
|
||||||
|
const parsed = rawDate ? new Date(rawDate) : null;
|
||||||
|
const valid = parsed && !Number.isNaN(parsed.valueOf()) ? parsed : null;
|
||||||
|
return [entry.name.replace(/\.md$/, ''), valid];
|
||||||
|
})
|
||||||
|
.filter(([, date]) => date !== null)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://schmelczer.dev',
|
||||||
|
trailingSlash: 'ignore',
|
||||||
|
integrations: [
|
||||||
|
sitemap({
|
||||||
|
filter: (page) => {
|
||||||
|
const path = new URL(page).pathname;
|
||||||
|
return !/^\/tags\/[^/]+\/?$/.test(path) && path !== '/404/';
|
||||||
|
},
|
||||||
|
serialize(item) {
|
||||||
|
const url = new URL(item.url);
|
||||||
|
const match = url.pathname.match(/^\/articles\/([^/]+)\/?$/);
|
||||||
|
let lastmod = item.lastmod;
|
||||||
|
if (match) {
|
||||||
|
const date = postLastmodLookup.get(match[1]);
|
||||||
|
if (date instanceof Date && !Number.isNaN(date.valueOf())) {
|
||||||
|
lastmod = date.toISOString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ...item, changefreq: 'monthly', ...(lastmod ? { lastmod } : {}) };
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
image: {
|
||||||
|
service: { entrypoint: 'astro/assets/services/sharp' },
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
watch: {
|
||||||
|
// Avoid inotify instance limits in dev containers and mounted volumes.
|
||||||
|
usePolling: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
|
shikiConfig: {
|
||||||
|
themes: {
|
||||||
|
light: 'github-light',
|
||||||
|
dark: 'github-dark',
|
||||||
|
},
|
||||||
|
defaultColor: false,
|
||||||
|
wrap: false,
|
||||||
|
},
|
||||||
|
rehypePlugins: [
|
||||||
|
rehypeSlug,
|
||||||
|
[
|
||||||
|
rehypeAutolinkHeadings,
|
||||||
|
{
|
||||||
|
behavior: 'append',
|
||||||
|
properties: {
|
||||||
|
className: ['heading-anchor'],
|
||||||
|
},
|
||||||
|
// Glyph rendered via CSS ::before so it doesn't leak into the TOC
|
||||||
|
// when astro:content extracts heading.text from the rendered HTML.
|
||||||
|
content: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Make scrollable code blocks and tables reachable via keyboard (WCAG
|
||||||
|
// 2.1.1): without tabindex, a keyboard user cannot focus a horizontally
|
||||||
|
// overflowing <pre> or <table> to scroll it. tabindex=0 is sufficient
|
||||||
|
// on its own; role=region would require a meaningful per-block label,
|
||||||
|
// which we don't have at markdown level.
|
||||||
|
function rehypeFocusableScrollables() {
|
||||||
|
const SCROLLABLE = new Set(['pre', 'table']);
|
||||||
|
return (tree) => {
|
||||||
|
visit(tree, 'element', (node) => {
|
||||||
|
if (!SCROLLABLE.has(node.tagName)) return;
|
||||||
|
node.properties.tabindex = '0';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
function rehypeLabelHeadingPermalinks() {
|
||||||
|
function textOf(node) {
|
||||||
|
if (!node) return '';
|
||||||
|
if (node.type === 'text') return node.value ?? '';
|
||||||
|
return (node.children ?? []).map(textOf).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (tree) => {
|
||||||
|
visit(tree, 'element', (node) => {
|
||||||
|
if (!/^h[2-6]$/.test(node.tagName)) return;
|
||||||
|
const headingText = textOf(node).trim();
|
||||||
|
if (!headingText) return;
|
||||||
|
|
||||||
|
for (const child of node.children ?? []) {
|
||||||
|
const className = child.properties?.className;
|
||||||
|
const classes = Array.isArray(className) ? className : [className];
|
||||||
|
if (child.tagName === 'a' && classes.includes('heading-anchor')) {
|
||||||
|
child.properties.ariaLabel = `Permalink to ${headingText}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
39
custom.d.ts
vendored
39
custom.d.ts
vendored
|
|
@ -1,39 +0,0 @@
|
||||||
declare module '*.svg' {
|
|
||||||
const content: string;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.jpg' {
|
|
||||||
import { ResponsiveImage } from 'src/types/responsive-image';
|
|
||||||
const content: ResponsiveImage;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.png' {
|
|
||||||
import { ResponsiveImage } from 'src/types/responsive-image';
|
|
||||||
const content: ResponsiveImage;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.mp4' {
|
|
||||||
import { url } from 'src/types/url';
|
|
||||||
const content: url;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.webm' {
|
|
||||||
import { url } from 'src/types/url';
|
|
||||||
const content: url;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.pdf' {
|
|
||||||
import { url } from 'src/types/url';
|
|
||||||
const content: url;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.html' {
|
|
||||||
const content: string;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
25139
package-lock.json
generated
25139
package-lock.json
generated
File diff suppressed because it is too large
Load diff
78
package.json
78
package.json
|
|
@ -1,60 +1,56 @@
|
||||||
{
|
{
|
||||||
"name": "portfolio",
|
"name": "schmelczer-dev",
|
||||||
"description": "An easily configurable timeline of projects.",
|
"description": "A static personal blog for Andras Schmelczer.",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"packageManager": "npm@10.9.2",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.13.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack serve --open --mode development",
|
"dev": "astro dev",
|
||||||
"lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.(ts|scss|json|html)\"",
|
"start": "astro dev",
|
||||||
"build": "webpack --mode production",
|
"typecheck": "astro check",
|
||||||
"update": "ncu"
|
"lint": "prettier --check \"astro.config.mjs\" \"src/**/*.{astro,ts,md,css}\" \"scripts/*.mjs\" \"*.md\" \"*.json\"",
|
||||||
|
"format": "prettier --write \"astro.config.mjs\" \"src/**/*.{astro,ts,md,css}\" \"scripts/*.mjs\" \"*.md\" \"*.json\"",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"qa:links": "node scripts/check-links.mjs",
|
||||||
|
"qa:no-js": "node scripts/check-no-js.mjs",
|
||||||
|
"qa:overflow": "node scripts/install-playwright-deps.mjs && node scripts/check-overflow.mjs",
|
||||||
|
"qa": "npm run typecheck && npm run lint && npm run build && npm run qa:links && npm run qa:no-js && npm run qa:overflow"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/schmelczer/schmelczer.github.io.git"
|
"url": "git+https://github.com/schmelczer/schmelczer.github.io.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"CV",
|
"blog",
|
||||||
"curriculum",
|
"software engineering",
|
||||||
"vitae",
|
"computer science",
|
||||||
"portfolio",
|
"portfolio"
|
||||||
"resumé"
|
|
||||||
],
|
],
|
||||||
"author": "Andras Schmelczer",
|
"author": "Andras Schmelczer",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/schmelczer/schmelczer.github.io/issues"
|
"url": "https://github.com/schmelczer/schmelczer.github.io/issues"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
|
||||||
"defaults"
|
|
||||||
],
|
|
||||||
"homepage": "https://github.com/schmelczer/schmelczer.github.io#readme",
|
"homepage": "https://github.com/schmelczer/schmelczer.github.io#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@plausible-analytics/tracker": "^0.4.0",
|
"@astrojs/check": "^0.9.9",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
"@astrojs/rss": "^4.0.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"css-loader": "^6.8.1",
|
"astro": "^6.3.1",
|
||||||
"eslint": "^8.50.0",
|
"playwright": "^1.59.1",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"prettier": "^3.8.3",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"html-webpack-plugin": "^5.5.3",
|
"rehype-slug": "^6.0.0",
|
||||||
"inline-source-webpack-plugin": "^3.0.1",
|
"typescript": "^5.9.3",
|
||||||
"mini-css-extract-plugin": "^2.7.6",
|
"unist-util-visit": "^5.1.0",
|
||||||
"npm-check-updates": "^16.14.4",
|
"sharp": "^0.34.5"
|
||||||
"prettier": "^3.0.3",
|
},
|
||||||
"resolve-url-loader": "^5.0.0",
|
"overrides": {
|
||||||
"responsive-loader": "^3.1.2",
|
"yaml": "^2.9.0"
|
||||||
"sass": "^1.68.0",
|
|
||||||
"sass-loader": "^13.3.2",
|
|
||||||
"sharp": "^0.32.6",
|
|
||||||
"sitemap-webpack-plugin": "^1.1.1",
|
|
||||||
"string-replace-loader": "^3.1.0",
|
|
||||||
"svg-inline-loader": "^0.8.2",
|
|
||||||
"terser-webpack-plugin": "^5.3.9",
|
|
||||||
"ts-loader": "^9.4.4",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"webpack": "^5.88.2",
|
|
||||||
"webpack-cli": "^5.1.4",
|
|
||||||
"webpack-dev-server": "^4.15.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/",
|
"types": ["astro/client"]
|
||||||
"noImplicitAny": false,
|
|
||||||
"module": "es6",
|
|
||||||
"target": "es5",
|
|
||||||
"sourceMap": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"strict": true,
|
|
||||||
"allowJs": true,
|
|
||||||
"lib": [
|
|
||||||
"dom"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
|
||||||
const InlineSourceWebpackPlugin = require('inline-source-webpack-plugin');
|
|
||||||
const SitemapPlugin = require('sitemap-webpack-plugin').default;
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
|
|
||||||
const domain = 'schmelczer.dev';
|
|
||||||
|
|
||||||
module.exports = (env, argv) => ({
|
|
||||||
devtool: argv.mode === 'development' ? 'inline-source-map' : false,
|
|
||||||
entry: {
|
|
||||||
index: './src/index.ts',
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
allowedHosts: 'all',
|
|
||||||
},
|
|
||||||
watchOptions: {
|
|
||||||
ignored: '**/node_modules',
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimizer: [
|
|
||||||
new TerserPlugin({
|
|
||||||
terserOptions: {
|
|
||||||
module: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
assetFilter: (f) => !/\.(webm|mp4|pdf)$/.test(f),
|
|
||||||
maxEntrypointSize: 100000,
|
|
||||||
maxAssetSize: 512000,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new SitemapPlugin({
|
|
||||||
base: `https://${domain}`,
|
|
||||||
paths: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
priority: 1,
|
|
||||||
changefreq: 'daily',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: './src/index.html',
|
|
||||||
}),
|
|
||||||
new MiniCssExtractPlugin(),
|
|
||||||
argv.mode === 'production'
|
|
||||||
? new InlineSourceWebpackPlugin({
|
|
||||||
compress: true,
|
|
||||||
})
|
|
||||||
: null,
|
|
||||||
new (require('webpack').DefinePlugin)({
|
|
||||||
__CURRENT_DATE__: Date.now(),
|
|
||||||
}),
|
|
||||||
].filter(Boolean),
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(jpe?g|png)$/i,
|
|
||||||
exclude: /no-change/i,
|
|
||||||
loader: 'responsive-loader',
|
|
||||||
options: {
|
|
||||||
adapter: require('responsive-loader/sharp'),
|
|
||||||
sizes: [200, 500, 900, 1400, 1920],
|
|
||||||
placeholder: true,
|
|
||||||
placeholderSize: 64,
|
|
||||||
quality: 85,
|
|
||||||
format: 'webp',
|
|
||||||
progressive: true,
|
|
||||||
name: '[hash:8].[ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(webm|mp4|woff2?)$/i,
|
|
||||||
type: 'asset/resource',
|
|
||||||
generator: {
|
|
||||||
filename: '[hash:8][ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.svg$/i,
|
|
||||||
use: 'svg-inline-loader',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\/no-change\//i,
|
|
||||||
type: 'asset/resource',
|
|
||||||
generator: {
|
|
||||||
filename: '[name][ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /.pdf$/i,
|
|
||||||
type: 'asset/resource',
|
|
||||||
generator: {
|
|
||||||
filename: 'static/[name][ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/i,
|
|
||||||
use: [
|
|
||||||
MiniCssExtractPlugin.loader,
|
|
||||||
'css-loader',
|
|
||||||
'resolve-url-loader',
|
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: true, // required by resolve-url-loader
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.ts$/,
|
|
||||||
use: [
|
|
||||||
argv.mode === 'production'
|
|
||||||
? {
|
|
||||||
// for removing whitespace (mainly from template strings) which are not part of comments
|
|
||||||
loader: 'string-replace-loader',
|
|
||||||
options: {
|
|
||||||
search: /(?<!\/\/[^\n]*)(\\n|\s)+/gs,
|
|
||||||
replace: ' ',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
'ts-loader',
|
|
||||||
].filter(Boolean),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [
|
|
||||||
'.ts',
|
|
||||||
'.js', // required for development
|
|
||||||
],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
clean: true,
|
|
||||||
filename: '[name].js',
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
publicPath: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue