Merge branch 'main' into vaporwave

This commit is contained in:
SleeplessOne1917 2023-07-01 10:31:35 -04:00 committed by GitHub
commit f55b804685
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 2160 additions and 2605 deletions

View file

@ -2,3 +2,4 @@ src/shared/translations
lemmy-translations lemmy-translations
src/assets/css/themes/*.css src/assets/css/themes/*.css
stats.json stats.json
dist

View file

@ -27,7 +27,7 @@ COPY .git .git
RUN echo "export const VERSION = '$(git describe --tag)';" > "src/shared/version.ts" RUN echo "export const VERSION = '$(git describe --tag)';" > "src/shared/version.ts"
RUN yarn --production --prefer-offline RUN yarn --production --prefer-offline
RUN yarn build:prod RUN NODE_OPTIONS="--max-old-space-size=8192" yarn build:prod
# Prune the image # Prune the image
RUN node-prune /usr/src/app/node_modules RUN node-prune /usr/src/app/node_modules

View file

@ -20,6 +20,7 @@ COPY generate_translations.js \
COPY lemmy-translations lemmy-translations COPY lemmy-translations lemmy-translations
COPY src src COPY src src
COPY .git .git
# Set UI version # Set UI version
RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts" RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts"

View file

@ -1,6 +1,6 @@
{ {
"name": "lemmy-ui", "name": "lemmy-ui",
"version": "0.18.1-rc.3", "version": "0.18.1-rc.7",
"description": "An isomorphic UI for lemmy", "description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui", "repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0", "license": "AGPL-3.0",
@ -8,9 +8,9 @@
"scripts": { "scripts": {
"analyze": "webpack --mode=none", "analyze": "webpack --mode=none",
"prebuild:dev": "yarn clean && node generate_translations.js", "prebuild:dev": "yarn clean && node generate_translations.js",
"build:dev": "webpack --mode=development", "build:dev": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development",
"prebuild:prod": "yarn clean && node generate_translations.js", "prebuild:prod": "yarn clean && node generate_translations.js",
"build:prod": "webpack --mode=production", "build:prod": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=production",
"clean": "yarn run rimraf dist", "clean": "yarn run rimraf dist",
"dev": "yarn build:dev --watch", "dev": "yarn build:dev --watch",
"lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"", "lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
@ -48,11 +48,11 @@
"check-password-strength": "^2.0.7", "check-password-strength": "^2.0.7",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"cookie": "^0.5.0",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"emoji-mart": "^5.4.0", "emoji-mart": "^5.4.0",
"emoji-short-name": "^2.0.0", "emoji-short-name": "^2.0.0",
"express": "~4.18.2", "express": "~4.18.2",
@ -66,7 +66,6 @@
"inferno-i18next-dess": "0.0.2", "inferno-i18next-dess": "0.0.2",
"inferno-router": "^8.1.1", "inferno-router": "^8.1.1",
"inferno-server": "^8.1.1", "inferno-server": "^8.1.1",
"isomorphic-cookie": "^1.2.4",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lemmy-js-client": "0.18.0-rc.2", "lemmy-js-client": "0.18.0-rc.2",
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
@ -98,6 +97,7 @@
"@babel/core": "^7.21.8", "@babel/core": "^7.21.8",
"@types/autosize": "^4.0.0", "@types/autosize": "^4.0.0",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/cookie": "^0.5.1",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/html-to-text": "^9.0.0", "@types/html-to-text": "^9.0.0",
"@types/lodash.isequal": "^4.5.6", "@types/lodash.isequal": "^4.5.6",
@ -126,6 +126,7 @@
"style-loader": "^3.3.2", "style-loader": "^3.3.2",
"terser": "^5.17.3", "terser": "^5.17.3",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"typescript-language-server": "^3.3.2",
"webpack-bundle-analyzer": "^4.9.0", "webpack-bundle-analyzer": "^4.9.0",
"webpack-dev-server": "4.15.0" "webpack-dev-server": "4.15.0"
}, },

View file

@ -1,11 +1,11 @@
import { initializeSite, isAuthPath } from "@utils/app"; import { initializeSite, isAuthPath } from "@utils/app";
import { getHttpBaseInternal } from "@utils/env"; import { getHttpBaseInternal } from "@utils/env";
import { ErrorPageData } from "@utils/types"; import { ErrorPageData } from "@utils/types";
import * as cookie from "cookie";
import fetch from "cross-fetch"; import fetch from "cross-fetch";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { StaticRouter, matchPath } from "inferno-router"; import { StaticRouter, matchPath } from "inferno-router";
import { renderToString } from "inferno-server"; import { renderToString } from "inferno-server";
import IsomorphicCookie from "isomorphic-cookie";
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client"; import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
import { App } from "../../shared/components/app/app"; import { App } from "../../shared/components/app/app";
import { import {
@ -25,11 +25,15 @@ import { setForwardedHeaders } from "../utils/set-forwarded-headers";
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {
try { try {
const activeRoute = routes.find(route => matchPath(req.path, route)); const activeRoute = routes.find(route => matchPath(req.path, route));
let auth: string | undefined = IsomorphicCookie.load("jwt", req);
let auth = req.headers.cookie
? cookie.parse(req.headers.cookie).jwt
: undefined;
const getSiteForm: GetSite = { auth }; const getSiteForm: GetSite = { auth };
const headers = setForwardedHeaders(req.headers); const headers = setForwardedHeaders(req.headers);
const client = wrapClient( const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers }) new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
); );
@ -43,6 +47,7 @@ export default async (req: Request, res: Response) => {
let routeData: RouteData = {}; let routeData: RouteData = {};
let errorPageData: ErrorPageData | undefined = undefined; let errorPageData: ErrorPageData | undefined = undefined;
let try_site = await client.getSite(getSiteForm); let try_site = await client.getSite(getSiteForm);
if (try_site.state === "failed" && try_site.msg == "not_logged_in") { if (try_site.state === "failed" && try_site.msg == "not_logged_in") {
console.error( console.error(
"Incorrect JWT token, skipping auth so frontend can remove jwt cookie" "Incorrect JWT token, skipping auth so frontend can remove jwt cookie"
@ -91,6 +96,7 @@ export default async (req: Request, res: Response) => {
// Redirect to the 404 if there's an API error // Redirect to the 404 if there's an API error
if (error) { if (error) {
console.error(error.msg); console.error(error.msg);
if (error.msg === "instance_is_private") { if (error.msg === "instance_is_private") {
return res.redirect(`/signup`); return res.redirect(`/signup`);
} else { } else {
@ -119,6 +125,7 @@ export default async (req: Request, res: Response) => {
// If an error is caught here, the error page couldn't even be rendered // If an error is caught here, the error page couldn't even be rendered
console.error(err); console.error(err);
res.statusCode = 500; res.statusCode = 500;
return res.send( return res.send(
process.env.NODE_ENV === "development" ? err.message : "Server error" process.env.NODE_ENV === "development" ? err.message : "Server error"
); );

View file

@ -1,4 +1,5 @@
import { setupDateFns } from "@utils/app"; import { setupDateFns } from "@utils/app";
import { getStaticDir } from "@utils/env";
import express from "express"; import express from "express";
import path from "path"; import path from "path";
import process from "process"; import process from "process";
@ -19,7 +20,13 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"]
server.use(express.json()); server.use(express.json());
server.use(express.urlencoded({ extended: false })); server.use(express.urlencoded({ extended: false }));
server.use("/static", express.static(path.resolve("./dist"))); server.use(
getStaticDir(),
express.static(path.resolve("./dist"), {
maxAge: 24 * 60 * 60 * 1000, // 1 day
immutable: true,
})
);
server.use(setCacheControl); server.use(setCacheControl);
if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) { if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {

View file

@ -1,5 +1,5 @@
import type { NextFunction, Response } from "express"; import type { NextFunction, Request, Response } from "express";
import { UserService } from "../shared/services"; import { hasJwtCookie } from "./utils/has-jwt-cookie";
export function setDefaultCsp({ export function setDefaultCsp({
res, res,
@ -18,24 +18,35 @@ export function setDefaultCsp({
// Set cache-control headers. If user is logged in, set `private` to prevent storing data in // Set cache-control headers. If user is logged in, set `private` to prevent storing data in
// shared caches (eg nginx) and leaking of private data. If user is not logged in, allow caching // shared caches (eg nginx) and leaking of private data. If user is not logged in, allow caching
// all responses for 60 seconds to reduce load on backend and database. The specific cache // all responses for 5 seconds to reduce load on backend and database. The specific cache
// interval is rather arbitrary and could be set higher (less server load) or lower (fresher data). // interval is rather arbitrary and could be set higher (less server load) or lower (fresher data).
// //
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
export function setCacheControl({ export function setCacheControl(
res, req: Request,
next, res: Response,
}: { next: NextFunction
res: Response; ) {
next: NextFunction; if (process.env.NODE_ENV !== "production") {
}) { return next();
const user = UserService.Instance;
let caching: string;
if (user.auth()) {
caching = "private";
} else {
caching = "public, max-age=60";
} }
let caching: string;
if (
req.path.match(/\.(js|css|txt|manifest\.webmanifest)\/?$/) ||
req.path.includes("/css/themelist")
) {
// Static content gets cached publicly for a day
caching = "public, max-age=86400";
} else {
if (hasJwtCookie(req)) {
caching = "private";
} else {
caching = "public, max-age=5";
}
}
res.setHeader("Cache-Control", caching); res.setHeader("Cache-Control", caching);
next(); next();

View file

@ -1,3 +1,4 @@
import { getStaticDir } from "@utils/env";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { renderToString } from "inferno-server"; import { renderToString } from "inferno-server";
import serialize from "serialize-javascript"; import serialize from "serialize-javascript";
@ -23,7 +24,7 @@ export async function createSsrHtml(
if (!appleTouchIcon) { if (!appleTouchIcon) {
appleTouchIcon = site?.site_view.site.icon appleTouchIcon = site?.site_view.site.icon
? `data:image/png;base64,${sharp( ? `data:image/png;base64,${await sharp(
await fetchIconPng(site.site_view.site.icon) await fetchIconPng(site.site_view.site.icon)
) )
.resize(180, 180) .resize(180, 180)
@ -87,7 +88,7 @@ export async function createSsrHtml(
<link rel="apple-touch-startup-image" href=${appleTouchIcon} /> <link rel="apple-touch-startup-image" href=${appleTouchIcon} />
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" type="text/css" href="/static/styles/styles.css" /> <link rel="stylesheet" type="text/css" href="${getStaticDir()}/styles/styles.css" />
<!-- Current theme and more --> <!-- Current theme and more -->
${helmet.link.toString() || fallbackTheme} ${helmet.link.toString() || fallbackTheme}
@ -102,7 +103,7 @@ export async function createSsrHtml(
</noscript> </noscript>
<div id='root'>${root}</div> <div id='root'>${root}</div>
<script defer src='/static/js/client.js'></script> <script defer src='${getStaticDir()}/js/client.js'></script>
</body> </body>
</html> </html>
`; `;

View file

@ -0,0 +1,6 @@
import * as cookie from "cookie";
import type { Request } from "express";
export function hasJwtCookie(req: Request): boolean {
return Boolean(cookie.parse(req.headers.cookie ?? "").jwt?.length);
}

View file

@ -1,3 +1,4 @@
import { getStaticDir } from "@utils/env";
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { I18NextService } from "../../services"; import { I18NextService } from "../../services";
@ -23,7 +24,9 @@ export class Icon extends Component<IconProps, any> {
})} })}
> >
<use <use
xlinkHref={`/static/assets/symbols.svg#icon-${this.props.icon}`} xlinkHref={`${getStaticDir()}/assets/symbols.svg#icon-${
this.props.icon
}`}
></use> ></use>
<div className="visually-hidden"> <div className="visually-hidden">
<title>{this.props.icon}</title> <title>{this.props.icon}</title>

View file

@ -1,5 +1,5 @@
import { capitalizeFirstLetter, formatPastDate } from "@utils/helpers"; import { capitalizeFirstLetter, formatPastDate } from "@utils/helpers";
import { formatInTimeZone } from "date-fns-tz"; import { format } from "date-fns";
import parseISO from "date-fns/parseISO"; import parseISO from "date-fns/parseISO";
import { Component } from "inferno"; import { Component } from "inferno";
import { I18NextService } from "../../services"; import { I18NextService } from "../../services";
@ -13,9 +13,8 @@ interface MomentTimeProps {
} }
function formatDate(input: string) { function formatDate(input: string) {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const parsed = parseISO(input + "Z"); const parsed = parseISO(input + "Z");
return formatInTimeZone(parsed, tz, "PPPPpppp"); return format(parsed, "PPPPpppp");
} }
export class MomentTime extends Component<MomentTimeProps, any> { export class MomentTime extends Component<MomentTimeProps, any> {

View file

@ -279,13 +279,15 @@ export class Home extends Component<any, HomeState> {
trendingCommunitiesRes, trendingCommunitiesRes,
commentsRes, commentsRes,
postsRes, postsRes,
tagline: getRandomFromList(this.state?.siteRes?.taglines ?? [])
?.content,
isIsomorphic: true, isIsomorphic: true,
}; };
HomeCacheService.postsRes = postsRes; HomeCacheService.postsRes = postsRes;
} }
this.state.tagline = getRandomFromList(
this.state?.siteRes?.taglines ?? []
)?.content;
} }
componentWillUnmount() { componentWillUnmount() {

View file

@ -141,7 +141,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) { handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
event.preventDefault(); event.preventDefault();
if (this.state.editingRow == d.index) { if (d.i.state.editingRow == d.index) {
d.i.setState({ editingRow: undefined }); d.i.setState({ editingRow: undefined });
} else { } else {
d.i.setState({ editingRow: d.index }); d.i.setState({ editingRow: d.index });

View file

@ -1,4 +1,5 @@
import { showAvatars } from "@utils/app"; import { showAvatars } from "@utils/app";
import { getStaticDir } from "@utils/env";
import { hostname, isCakeDay } from "@utils/helpers"; import { hostname, isCakeDay } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
@ -88,7 +89,7 @@ export class PersonListing extends Component<PersonListingProps, any> {
!this.props.person.banned && !this.props.person.banned &&
showAvatars() && ( showAvatars() && (
<PictrsImage <PictrsImage
src={avatar ?? "/static/assets/icons/icon-96x96.png"} src={avatar ?? `${getStaticDir()}/assets/icons/icon-96x96.png`}
icon icon
/> />
)} )}

View file

@ -333,7 +333,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
<button <button
type="button" type="button"
className="thumbnail rounded overflow-hidden d-inline-block position-relative mb-2 p-0 border-0" className="thumbnail rounded overflow-hidden d-inline-block position-relative p-0 border-0"
data-tippy-content={I18NextService.i18n.t("expand_here")} data-tippy-content={I18NextService.i18n.t("expand_here")}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
aria-label={I18NextService.i18n.t("expand_here")} aria-label={I18NextService.i18n.t("expand_here")}
@ -348,7 +348,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} else if (!this.props.hideImage && url && thumbnail && this.imageSrc) { } else if (!this.props.hideImage && url && thumbnail && this.imageSrc) {
return ( return (
<a <a
className="thumbnail rounded bg-light d-flex justify-content-center" className="thumbnail rounded overflow-hidden d-inline-block position-relative p-0 border-0"
href={url} href={url}
rel={relTags} rel={relTags}
title={url} title={url}
@ -403,8 +403,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
createdLine() { createdLine() {
const post_view = this.postView; const post_view = this.postView;
return ( return (
<div className="small"> <div className="small mb-1 mb-md-0">
<span className="me-1"> <span className="me-1">
<PersonListing person={post_view.creator} /> <PersonListing person={post_view.creator} />
</span> </span>

View file

@ -1,5 +1,7 @@
export const favIconUrl = "/static/assets/icons/favicon.svg"; import { getStaticDir } from "@utils/env";
export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
export const favIconUrl = `${getStaticDir()}/assets/icons/favicon.svg`;
export const favIconPngUrl = `${getStaticDir()}/assets/icons/apple-touch-icon.png`;
export const repoUrl = "https://github.com/LemmyNet"; export const repoUrl = "https://github.com/LemmyNet";
export const joinLemmyUrl = "https://join-lemmy.org"; export const joinLemmyUrl = "https://join-lemmy.org";

View file

@ -2,7 +2,7 @@
import { isAuthPath } from "@utils/app"; import { isAuthPath } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { isHttps } from "@utils/env"; import { isHttps } from "@utils/env";
import IsomorphicCookie from "isomorphic-cookie"; import * as cookie from "cookie";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import { LoginResponse, MyUserInfo } from "lemmy-js-client"; import { LoginResponse, MyUserInfo } from "lemmy-js-client";
import { toast } from "../toast"; import { toast } from "../toast";
@ -31,9 +31,14 @@ export class UserService {
public login(res: LoginResponse) { public login(res: LoginResponse) {
const expires = new Date(); const expires = new Date();
expires.setDate(expires.getDate() + 365); expires.setDate(expires.getDate() + 365);
if (res.jwt) { if (isBrowser() && res.jwt) {
toast(I18NextService.i18n.t("logged_in")); toast(I18NextService.i18n.t("logged_in"));
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps() }); document.cookie = cookie.serialize("jwt", res.jwt, {
expires,
secure: isHttps(),
domain: location.hostname,
sameSite: true,
});
this.#setJwtInfo(); this.#setJwtInfo();
} }
} }
@ -41,8 +46,14 @@ export class UserService {
public logout() { public logout() {
this.jwtInfo = undefined; this.jwtInfo = undefined;
this.myUserInfo = undefined; this.myUserInfo = undefined;
IsomorphicCookie.remove("jwt"); // TODO is sometimes unreliable for some reason if (isBrowser()) {
document.cookie = "jwt=; Max-Age=0; path=/; domain=" + location.hostname; document.cookie = cookie.serialize("jwt", "", {
maxAge: 0,
path: "/",
domain: location.hostname,
sameSite: true,
});
}
if (isAuthPath(location.pathname)) { if (isAuthPath(location.pathname)) {
location.replace("/"); location.replace("/");
} else { } else {
@ -66,10 +77,11 @@ export class UserService {
} }
#setJwtInfo() { #setJwtInfo() {
const jwt: string | undefined = IsomorphicCookie.load("jwt"); if (isBrowser()) {
const { jwt } = cookie.parse(document.cookie);
if (jwt) { if (jwt) {
this.jwtInfo = { jwt, claims: jwt_decode(jwt) }; this.jwtInfo = { jwt, claims: jwt_decode(jwt) };
}
} }
} }

View file

@ -1,5 +1,5 @@
export default function isAuthPath(pathname: string) { export default function isAuthPath(pathname: string) {
return /create_.*|inbox|settings|admin|reports|registration_applications/g.test( return /^\/create_.*|inbox|settings|admin|reports|registration_applications/g.test(
pathname pathname
); );
} }

View file

@ -7,6 +7,16 @@ export default async function () {
lang = "en-US"; lang = "en-US";
} }
// if lang and country are the same, then date-fns expects only the lang
// eg: instead of "fr-FR", we should import just "fr"
if (lang.includes("-")) {
const parts = lang.split("-");
if (parts[0] === parts[1].toLowerCase()) {
lang = parts[0];
}
}
const locale = ( const locale = (
await import( await import(
/* webpackExclude: /\.js\.flow$/ */ /* webpackExclude: /\.js\.flow$/ */

View file

@ -0,0 +1,5 @@
// Returns path to static directory, intended
// for cache-busting based on latest commit hash.
export default function getStaticDir() {
return `/static/${process.env.COMMIT_HASH}`;
}

View file

@ -6,6 +6,7 @@ import getHttpBaseExternal from "./get-http-base-external";
import getHttpBaseInternal from "./get-http-base-internal"; import getHttpBaseInternal from "./get-http-base-internal";
import getInternalHost from "./get-internal-host"; import getInternalHost from "./get-internal-host";
import getSecure from "./get-secure"; import getSecure from "./get-secure";
import getStaticDir from "./get-static-dir";
import httpExternalPath from "./http-external-path"; import httpExternalPath from "./http-external-path";
import isHttps from "./is-https"; import isHttps from "./is-https";
@ -18,6 +19,7 @@ export {
getHttpBaseInternal, getHttpBaseInternal,
getInternalHost, getInternalHost,
getSecure, getSecure,
getStaticDir,
httpExternalPath, httpExternalPath,
isHttps, isHttps,
}; };

View file

@ -14,56 +14,63 @@ const banner = `
@license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0 @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0
`; `;
const base = { function getBase(env, mode) {
output: { return {
filename: "js/server.js", output: {
publicPath: "/", filename: "js/server.js",
hashFunction: "xxhash64", publicPath: "/",
}, hashFunction: "xxhash64",
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
alias: {
"@": path.resolve(__dirname, "src/"),
"@utils": path.resolve(__dirname, "src/shared/utils/"),
}, },
}, resolve: {
performance: { extensions: [".js", ".jsx", ".ts", ".tsx"],
hints: false, alias: {
}, "@": path.resolve(__dirname, "src/"),
module: { "@utils": path.resolve(__dirname, "src/shared/utils/"),
rules: [
{
test: /\.(scss|css)$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
}, },
{ },
test: /\.(js|jsx|tsx|ts)$/, // All ts and tsx files will be process by performance: {
exclude: /node_modules/, // ignore node_modules hints: false,
loader: "babel-loader", },
}, module: {
// Due to some weird babel issue: https://github.com/webpack/webpack/issues/11467 rules: [
{ {
test: /\.m?js/, test: /\.(scss|css)$/i,
resolve: { use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
fullySpecified: false,
}, },
}, {
test: /\.(js|jsx|tsx|ts)$/, // All ts and tsx files will be process by
exclude: /node_modules/, // ignore node_modules
loader: "babel-loader",
},
// Due to some weird babel issue: https://github.com/webpack/webpack/issues/11467
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
],
},
plugins: [
new webpack.DefinePlugin({
"process.env.COMMIT_HASH": `"${env.COMMIT_HASH}"`,
"process.env.NODE_ENV": `"${mode}"`,
}),
new MiniCssExtractPlugin({
filename: "styles/styles.css",
}),
new CopyPlugin({
patterns: [{ from: "./src/assets", to: "./assets" }],
}),
new webpack.BannerPlugin({
banner,
}),
], ],
}, };
plugins: [ }
new MiniCssExtractPlugin({
filename: "styles/styles.css",
}),
new CopyPlugin({
patterns: [{ from: "./src/assets", to: "./assets" }],
}),
new webpack.BannerPlugin({
banner,
}),
],
};
const createServerConfig = (_env, mode) => { const createServerConfig = (env, mode) => {
const base = getBase(env, mode);
const config = merge({}, base, { const config = merge({}, base, {
mode, mode,
entry: "./src/server/index.tsx", entry: "./src/server/index.tsx",
@ -90,22 +97,20 @@ const createServerConfig = (_env, mode) => {
return config; return config;
}; };
const createClientConfig = (_env, mode) => { const createClientConfig = (env, mode) => {
const base = getBase(env, mode);
const config = merge({}, base, { const config = merge({}, base, {
mode, mode,
entry: "./src/client/index.tsx", entry: "./src/client/index.tsx",
output: { output: {
filename: "js/client.js", filename: "js/client.js",
publicPath: "/static/", publicPath: `/static/${env.COMMIT_HASH}/`,
}, },
plugins: [ plugins: [
...base.plugins, ...base.plugins,
new ServiceWorkerPlugin({ new ServiceWorkerPlugin({
enableInDevelopment: mode !== "development", // this may seem counterintuitive, but it is correct enableInDevelopment: mode !== "development", // this may seem counterintuitive, but it is correct
workbox: { workbox: {
modifyURLPrefix: {
"/": "/static/",
},
cacheId: "lemmy", cacheId: "lemmy",
include: [/(assets|styles|js)\/.+\..+$/g], include: [/(assets|styles|js)\/.+\..+$/g],
inlineWorkboxRuntime: true, inlineWorkboxRuntime: true,

4486
yarn.lock

File diff suppressed because it is too large Load diff