Compare commits

..

No commits in common. "5fa69be1d586d4f0b344e517ca34b6d4832fe3c8" and "3393f404829f74b3b7c66ffb44ca47a6f8898690" have entirely different histories.

22 changed files with 3586 additions and 1631 deletions

3
.gitignore vendored
View file

@ -4,5 +4,4 @@
**/node_modules **/node_modules
test.* test.*
dist.tar.zst dist.tar.zst
.env .env
web/pkg

3
.swcrc
View file

@ -1,8 +1,7 @@
{ {
"jsc": { "jsc": {
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript"
"tsx": true
}, },
"target": "es2021" "target": "es2021"
} }

48
Cargo.lock generated
View file

@ -73,12 +73,11 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.4.4" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310a147401c66e79fc78636e4db63ac68cd6acb9ece056de806ea173a15bce32" checksum = "6b4e96976b2022b23b2199168ff9b281e9ddc1aa795607d5cb7146868ca5c101"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core",
"bitflags", "bitflags",
"bytes", "bytes",
"futures-util", "futures-util",
@ -87,7 +86,6 @@ dependencies = [
"http-body", "http-body",
"hyper", "hyper",
"matchit", "matchit",
"memchr",
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
@ -98,25 +96,11 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http 0.2.0", "tower-http",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
[[package]]
name = "axum-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca6c0b218388a7ed6a8d25e94f7dea5498daaa4fd8c711fb3ff166041b06fda"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -669,12 +653,6 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "http-range-header"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.5.1" version = "1.5.1"
@ -1034,7 +1012,7 @@ dependencies = [
"signal-hook", "signal-hook",
"signal-hook-tokio", "signal-hook-tokio",
"tokio", "tokio",
"tower-http 0.1.2", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -1753,24 +1731,6 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "tower-http"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ee603d6e665ecc7e0f8d479eedb4626bd4726f0ee6119cee5b3a6bf184cac0"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.1" version = "0.3.1"

17
Trunk.toml Normal file
View file

@ -0,0 +1,17 @@
[build]
target = "web/index.html"
release = true
dist = "./dist"
[clean]
dist = "./dist"
[[proxy]]
backend = "http://localhost:8081"
rewrite = "/api/"
[[hooks]]
stage="post_build"
command="npx"
command_arguments=["swc", "$TRUNK_SOURCE_DIR/src/main.ts", "-o", "$TRUNK_STAGING_DIR/main.js"]

View file

@ -20,16 +20,21 @@ set -euxo pipefail
cd "$(git rev-parse --show-toplevel)" || exit 1 cd "$(git rev-parse --show-toplevel)" || exit 1
# Clean resources # Build frontend assets
rm -rf dist
# Build frontend code
yarn yarn
yarn build trunk build --release
mv dist/static/index.html dist
sed -i 's#/index#/static/index#g' dist/index.html
sed -i 's#stylesheet" href="/main#stylesheet" href="/static/main#g' dist/index.html
# Build server # Build server
cargo build --release --bin omegaupload-server cargo build --release --bin omegaupload-server
# Prepare assets for upload to webserver
mkdir -p dist/static
# Move everything that's not index.html into a `static` subdir
find dist -not -name index.html -type f -exec mv {} dist/static/ ";"
strip target/release/omegaupload-server strip target/release/omegaupload-server
cp target/release/omegaupload-server dist/omegaupload-server cp target/release/omegaupload-server dist/omegaupload-server

View file

@ -19,9 +19,17 @@
set -euxo pipefail set -euxo pipefail
CUR_DIR=$(pwd) CUR_DIR=$(pwd)
PROJECT_TOP_LEVEL=$(git rev-parse --show-toplevel) PROJECT_TOP_LEVEL=$(git rev-parse --show-toplevel)
cd "$PROJECT_TOP_LEVEL" || exit 1 cd "$PROJECT_TOP_LEVEL" || exit 1
git submodule foreach git pull git submodule foreach git pull
HLJS_PATH=$(git submodule status | cut -d ' ' -f3 | grep highlight.js)
cd "$HLJS_PATH"
npm ci # install without updating package-lock.josn
node tools/build
mv build/highlight.min.js "$PROJECT_TOP_LEVEL"/"$HLJS_PATH"/..
cd "$CUR_DIR" cd "$CUR_DIR"

View file

@ -1,26 +1,6 @@
{ {
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.51", "@swc/cli": "^0.1.51",
"@swc/core": "^1.2.102", "@swc/core": "^1.2.102"
"@types/react-dom": "^17.0.11",
"@wasm-tool/wasm-pack-plugin": "^1.6.0",
"css-loader": "^6.5.1",
"html-webpack-plugin": "^5.5.0",
"sass": "^1.48.0",
"sass-loader": "^12.4.0",
"style-loader": "^3.3.1",
"swc-loader": "^0.1.15",
"webpack": "^5.66.0",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"highlight.js": "^11.4.0",
"highlightjs-line-numbers.js": "^2.8.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"scripts": {
"build": "webpack --mode production",
"clean": "(git rev-parse --show-toplevel && rm -rf node_modules dist web/pkg)"
} }
} }

View file

@ -8,7 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
omegaupload-common = { path = "../common" } omegaupload-common = { path = "../common" }
anyhow = "1" anyhow = "1"
axum = { version = "0.4", features = ["http2", "headers"] } axum = { version = "0.3", features = ["http2", "headers"] }
bincode = "1" bincode = "1"
# We don't care about which version (We want to match with axum), we just need # We don't care about which version (We want to match with axum), we just need
# to enable the feature # to enable the feature

View file

@ -22,11 +22,12 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use axum::body::Bytes; use axum::body::Bytes;
use axum::error_handling::HandleError; use axum::error_handling::HandleErrorExt;
use axum::extract::{Extension, Path, TypedHeader}; use axum::extract::{Extension, Path, TypedHeader};
use axum::http::header::EXPIRES; use axum::http::header::EXPIRES;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::routing::{get, get_service, post}; use axum::response::Html;
use axum::routing::{get, post, service_method_routing};
use axum::{AddExtensionLayer, Router}; use axum::{AddExtensionLayer, Router};
use chrono::Utc; use chrono::Utc;
use futures::stream::StreamExt; use futures::stream::StreamExt;
@ -40,7 +41,7 @@ use rocksdb::{Options, DB};
use signal_hook::consts::SIGUSR1; use signal_hook::consts::SIGUSR1;
use signal_hook_tokio::Signals; use signal_hook_tokio::Signals;
use tokio::task::{self, JoinHandle}; use tokio::task::{self, JoinHandle};
use tower_http::services::{ServeDir, ServeFile}; use tower_http::services::ServeDir;
use tracing::{error, instrument, trace}; use tracing::{error, instrument, trace};
use tracing::{info, warn}; use tracing::{info, warn};
@ -57,6 +58,7 @@ lazy_static! {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
const INDEX_PAGE: Html<&'static str> = Html(include_str!("../../dist/index.html"));
const PASTE_DB_PATH: &str = "database"; const PASTE_DB_PATH: &str = "database";
const SHORT_CODE_SIZE: usize = 12; const SHORT_CODE_SIZE: usize = 12;
@ -81,13 +83,8 @@ async fn main() -> Result<()> {
let signals_handle = signals.handle(); let signals_handle = signals.handle();
let signals_task = tokio::spawn(handle_signals(signals, Arc::clone(&db))); let signals_task = tokio::spawn(handle_signals(signals, Arc::clone(&db)));
let root_service = HandleError::new(get_service(ServeDir::new("static")), |_| async { let root_service = service_method_routing::get(ServeDir::new("static"))
Ok::<_, Infallible>(StatusCode::NOT_FOUND) .handle_error(|_| Ok::<_, Infallible>(StatusCode::NOT_FOUND));
});
let index_service = HandleError::new(get_service(ServeFile::new("index.html")), |_| async {
Ok::<_, Infallible>(StatusCode::NOT_FOUND)
});
axum::Server::bind(&"0.0.0.0:8080".parse()?) axum::Server::bind(&"0.0.0.0:8080".parse()?)
.serve({ .serve({
@ -95,9 +92,9 @@ async fn main() -> Result<()> {
Router::new() Router::new()
.route( .route(
"/", "/",
post(upload::<SHORT_CODE_SIZE>).get_service(index_service.clone()), post(upload::<SHORT_CODE_SIZE>).get(|| async { INDEX_PAGE }),
) )
.route("/:code", index_service) .route("/:code", get(|| async { INDEX_PAGE }))
.nest("/static", root_service) .nest("/static", root_service)
.route( .route(
&format!("{API_ENDPOINT}/:code"), &format!("{API_ENDPOINT}/:code"),

View file

@ -3,8 +3,7 @@ name = "omegaupload-web"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[lib] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
crate-type = ["cdylib"]
[dependencies] [dependencies]
omegaupload-common = { path = "../common", features = ["wasm"] } omegaupload-common = { path = "../common", features = ["wasm"] }

22
web/index.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Omegaupload</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="static/main.js" defer></script>
<script src="static/highlight.min.js" defer></script>
<script src="static/highlightjs-line-numbers.min.js" defer></script>
<link data-trunk rel="rust" data-wasm-opt="0" data-keep-debug="true" data-no-mangle="true" />
<link data-trunk rel="copy-file" href="vendor/MPLUS_FONTS/fonts/ttf/MPLUSCodeLatin[wdth,wght].ttf" dest="/" />
<link data-trunk rel="copy-file" href="vendor/highlight.min.js" dest="/" />
<link data-trunk rel="copy-file" href="vendor/highlightjs-line-numbers.js/dist/highlightjs-line-numbers.min.js"
dest="/" />
<link data-trunk rel="scss" href="src/main.scss" />
</head>
</html>

1
web/src/globals.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare var hljs;

View file

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Omegaupload</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
</html>

View file

@ -1,3 +0,0 @@
import { start } from '../pkg';
start();

View file

@ -44,7 +44,7 @@ mod util;
const DOWNLOAD_SIZE_LIMIT: u128 = n_mib_bytes!(500); const DOWNLOAD_SIZE_LIMIT: u128 = n_mib_bytes!(500);
#[wasm_bindgen(raw_module = "../src/render")] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(js_name = loadFromDb)] #[wasm_bindgen(js_name = loadFromDb)]
pub fn load_from_db(mime_type: JsString, name: Option<JsString>, language: Option<JsString>); pub fn load_from_db(mime_type: JsString, name: Option<JsString>, language: Option<JsString>);
@ -69,8 +69,7 @@ fn open_idb() -> Result<IdbOpenDbRequest> {
.map_err(|_| anyhow!("Failed to open idb")) .map_err(|_| anyhow!("Failed to open idb"))
} }
#[wasm_bindgen] fn main() {
pub fn start() {
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
if location().pathname().unwrap() == "/" { if location().pathname().unwrap() == "/" {

View file

@ -14,13 +14,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
@use 'node_modules/highlight.js/styles/github-dark.css'; @use '../vendor/highlight.js/src/styles/github-dark.css';
$padding: 1em; $padding: 1em;
@font-face { @font-face {
font-family: "Mplus Code"; font-family: "Mplus Code";
src: url("../vendor/MPLUS_FONTS/fonts/ttf/MPLUSCodeLatin[wdth,wght].ttf") format("truetype"); src: url("./MPLUSCodeLatin[wdth,wght].ttf") format("truetype");
} }
body { body {
@ -43,10 +43,6 @@ main {
justify-content: center; justify-content: center;
} }
th {
font-weight: normal;
}
.paste { .paste {
@extend .hljs; @extend .hljs;
border-radius: $padding; border-radius: $padding;

View file

@ -14,14 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import './main.scss'; // Exported to main.rs
import ReactDom from 'react-dom';
import React from 'react';
const hljs = require('highlight.js');
(window as any).hljs = hljs;
require('highlightjs-line-numbers.js');
function loadFromDb(mimeType: string, name?: string, language?: string) { function loadFromDb(mimeType: string, name?: string, language?: string) {
let resolvedName; let resolvedName;
if (name) { if (name) {
@ -98,20 +91,36 @@ function loadFromDb(mimeType: string, name?: string, language?: string) {
} }
function createStringPasteUi(data, mimeType: string, name: string, lang?: string) { function createStringPasteUi(data, mimeType: string, name: string, lang?: string) {
const html = <main> const bodyEle = document.getElementsByTagName("body")[0];
<pre className='paste'> bodyEle.textContent = '';
<p className='unselectable centered'>{data.expiration}</p>
<a href={getObjectUrl([data.data], mimeType)} download={name} className='hljs-meta centered'>
Download file.
</a>
<hr />
<code>
{data.data}
</code>
</pre>
</main>;
ReactDom.render(html, document.body); const mainEle = document.createElement("main");
const preEle = document.createElement("pre");
preEle.classList.add("paste");
const headerEle = document.createElement("p");
headerEle.classList.add("unselectable");
headerEle.classList.add("centered");
headerEle.textContent = data.expiration;
preEle.appendChild(headerEle);
const downloadEle = document.createElement("a");
downloadEle.href = getObjectUrl([data.data], mimeType);
downloadEle.download = name;
downloadEle.classList.add("hljs-meta");
downloadEle.classList.add("centered");
downloadEle.textContent = "Download file.";
preEle.appendChild(downloadEle);
preEle.appendChild(document.createElement("hr"));
const codeEle = document.createElement("code");
codeEle.textContent = data.data;
preEle.appendChild(codeEle);
mainEle.appendChild(preEle);
bodyEle.appendChild(mainEle);
let languages = undefined; let languages = undefined;
@ -158,28 +167,46 @@ function createStringPasteUi(data, mimeType: string, name: string, lang?: string
} }
hljs.highlightAll(); hljs.highlightAll();
hljs.initLineNumbersOnLoad();
(hljs as any).initLineNumbersOnLoad();
} }
function createBlobPasteUi(data, name: string) { function createBlobPasteUi(data, name: string) {
const html = <main className='hljs centered fullscreen'> const bodyEle = document.getElementsByTagName("body")[0];
<div className='centered'> bodyEle.textContent = '';
<p>{data.expiration}</p>
<a href={getObjectUrl(data.data, name)} download={name} className='hljs-meta'>
Download binary file.
</a>
</div>
<p className='display-anyways hljs-comment' onClick={() => {
data.data.text().then(text => {
data.data = text;
createStringPasteUi(data, "application/octet-stream", name);
})
}}>Display anyways?</p>
</main>;
ReactDom.render(html, document.body); const mainEle = document.createElement("main");
mainEle.classList.add("hljs");
mainEle.classList.add("centered");
mainEle.classList.add("fullscreen");
const divEle = document.createElement("div");
divEle.classList.add("centered");
const expirationEle = document.createElement("p");
expirationEle.textContent = data.expiration;
divEle.appendChild(expirationEle);
const downloadEle = document.createElement("a");
downloadEle.href = getObjectUrl(data.data, name);
downloadEle.download = name;
downloadEle.classList.add("hljs-meta");
downloadEle.textContent = "Download binary file.";
divEle.appendChild(downloadEle);
mainEle.appendChild(divEle);
const displayAnywayEle = document.createElement("p");
displayAnywayEle.classList.add("display-anyways");
displayAnywayEle.classList.add("hljs-comment");
displayAnywayEle.textContent = "Display anyways?";
displayAnywayEle.onclick = () => {
data.data.text().then(text => {
data.data = text;
createStringPasteUi(data, "application/octet-stream", name);
})
};
mainEle.appendChild(displayAnywayEle);
bodyEle.appendChild(mainEle);
} }
function createImagePasteUi({ expiration, data, file_size }, name: string, mimeType: string) { function createImagePasteUi({ expiration, data, file_size }, name: string, mimeType: string) {
@ -201,6 +228,39 @@ function createVideoPasteUi({ expiration, data }, name: string, mimeType: string
} }
function createArchivePasteUi({ expiration, data, entries }, name: string) { function createArchivePasteUi({ expiration, data, entries }, name: string) {
const bodyEle = document.getElementsByTagName("body")[0];
bodyEle.textContent = '';
const mainEle = document.createElement("main");
const sectionEle = document.createElement("section");
sectionEle.classList.add("paste");
const expirationEle = document.createElement("p");
expirationEle.textContent = expiration;
expirationEle.classList.add("centered");
sectionEle.appendChild(expirationEle);
const downloadEle = document.createElement("a");
downloadEle.href = getObjectUrl(data);
downloadEle.download = name;
downloadEle.textContent = "Download";
downloadEle.classList.add("hljs-meta");
downloadEle.classList.add("centered");
sectionEle.appendChild(downloadEle);
sectionEle.appendChild(document.createElement("hr"));
const mediaEle = document.createElement("table");
mediaEle.classList.add("archive-table");
const tr = mediaEle.insertRow();
tr.classList.add("hljs-title");
const tdName = tr.insertCell();
tdName.textContent = "Name";
const tdSize = tr.insertCell();
tdSize.classList.add("align-right");
tdSize.textContent = "File Size";
// Because it's a stable sort, we can first sort by name (to get all folder // Because it's a stable sort, we can first sort by name (to get all folder
// items grouped together) and then sort by if there's a / or not. // items grouped together) and then sort by if there's a / or not.
entries.sort((a, b) => { entries.sort((a, b) => {
@ -213,32 +273,23 @@ function createArchivePasteUi({ expiration, data, entries }, name: string) {
return b.name.includes("/") - a.name.includes("/"); return b.name.includes("/") - a.name.includes("/");
}); });
const html = <main> for (const { name, file_size } of entries) {
<section className='paste'> const tr = mediaEle.insertRow();
<p className='centered'>{expiration}</p> const tdName = tr.insertCell();
<a href={getObjectUrl(data)} download={name} className='hljs-meta centered'>Download</a> tdName.textContent = name;
<hr /> const tdSize = tr.insertCell();
<table className='archive-table'> tdSize.textContent = file_size;
<thead> tdSize.classList.add("align-right");
<tr className='hljs-title'><th>Name</th><th className='align-right'>File Size</th></tr> tdSize.classList.add("hljs-number");
</thead> }
<tbody>
{
entries.map(({ name, file_size }) => {
return <tr><td>{name}</td><td className='align-right hljs-number'>{file_size}</td></tr>;
})
}
</tbody>
</table>
</section>
</main>;
ReactDom.render(html, document.body);
sectionEle.appendChild(mediaEle);
mainEle.appendChild(sectionEle);
bodyEle.appendChild(mainEle);
} }
function createMultiMediaPasteUi(tag, expiration, data, name: string, mimeType: string, on_create?: Function | string) { function createMultiMediaPasteUi(tag, expiration, data, name: string, mimeType: string, on_create?: Function | string) {
const bodyEle = document.body; const bodyEle = document.getElementsByTagName("body")[0];
bodyEle.textContent = ''; bodyEle.textContent = '';
const mainEle = document.createElement("main"); const mainEle = document.createElement("main");
@ -257,6 +308,7 @@ function createMultiMediaPasteUi(tag, expiration, data, name: string, mimeType:
mediaEle.controls = true; mediaEle.controls = true;
mainEle.appendChild(mediaEle); mainEle.appendChild(mediaEle);
const downloadEle = document.createElement("a"); const downloadEle = document.createElement("a");
downloadEle.href = downloadLink; downloadEle.href = downloadLink;
downloadEle.download = name; downloadEle.download = name;
@ -273,12 +325,14 @@ function createMultiMediaPasteUi(tag, expiration, data, name: string, mimeType:
} }
function renderMessage(message) { function renderMessage(message) {
ReactDom.render( const body = document.getElementsByTagName("body")[0];
<main className='hljs centered fullscreen'> body.textContent = '';
{message} const mainEle = document.createElement("main");
</main>, mainEle.classList.add("hljs");
document.body, mainEle.classList.add("centered");
); mainEle.classList.add("fullscreen");
mainEle.textContent = message;
body.appendChild(mainEle);
} }
function getObjectUrl(data, mimeType?: string) { function getObjectUrl(data, mimeType?: string) {
@ -286,5 +340,3 @@ function getObjectUrl(data, mimeType?: string) {
} }
window.addEventListener("hashchange", () => location.reload()); window.addEventListener("hashchange", () => location.reload());
export { renderMessage, loadFromDb };

1
web/vendor/highlight.js vendored Submodule

@ -0,0 +1 @@
Subproject commit 112135fb063af64b7a94155b5d86859e8d52b6f0

3377
web/vendor/highlight.min.js vendored Normal file

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
Subproject commit c2d9209ce356956b86316da3881b125539a2aaa3

View file

@ -1,49 +0,0 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './web/src/index.js',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'swc-loader',
exclude: /node_modules/,
},
{
test: /\.scss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
path: path.resolve(__dirname, 'dist/static'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'web/src/index.html'),
publicPath: "/static",
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, "web"),
outDir: path.resolve(__dirname, "web/pkg"),
}),
],
experiments: {
asyncWebAssembly: true,
},
mode: 'development'
};

1406
yarn.lock

File diff suppressed because it is too large Load diff