Ported to WASM + WebGPU. (Untested.)
I was not able to test this because: * Firefox nightly does not support the WebGPU spec of wgpu 0.13 * Chrome Dev WebGPU does not work consistently on AMD+Linux * I don't feel like setting up a Windows VM or reverting wgpu Pushing onto a separate branch for whenever Firefox gets updated, so I can test it then.wasm
parent
7fb393ddda
commit
a0ae573258
|
@ -7,3 +7,6 @@ charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{js, json, html}]
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -11,3 +11,13 @@
|
||||||
!/Cargo.lock
|
!/Cargo.lock
|
||||||
!/Cargo.toml
|
!/Cargo.toml
|
||||||
!/LICENSE.txt
|
!/LICENSE.txt
|
||||||
|
|
||||||
|
# www source code
|
||||||
|
!/www/bootstrap.js
|
||||||
|
!/www/index.js
|
||||||
|
!/www/index.html
|
||||||
|
|
||||||
|
# www configuration
|
||||||
|
!/www/package.json
|
||||||
|
!/www/package-lock.json
|
||||||
|
!/www/webpack.config.js
|
||||||
|
|
|
@ -297,6 +297,26 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "copyless"
|
name = "copyless"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -1179,11 +1199,16 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"claxon",
|
"claxon",
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"console_log",
|
||||||
"cpal",
|
"cpal",
|
||||||
"fern",
|
"fern",
|
||||||
"image",
|
"image",
|
||||||
"log",
|
"log",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
@ -1434,18 +1459,6 @@ checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-macros"
|
|
||||||
version = "1.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
41
Cargo.toml
41
Cargo.toml
|
@ -6,13 +6,14 @@ description = ""
|
||||||
repository = "https://github.com/jamestmartin/pathland"
|
repository = "https://github.com/jamestmartin/pathland"
|
||||||
license = "0BSD"
|
license = "0BSD"
|
||||||
publish = false
|
publish = false
|
||||||
|
autobins = false
|
||||||
|
|
||||||
[features]
|
[lib]
|
||||||
client = []
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
server = []
|
[[bin]]
|
||||||
|
name = "pathland"
|
||||||
[dependencies]
|
path = "src/main.rs"
|
||||||
|
|
||||||
# brotli (compression format)
|
# brotli (compression format)
|
||||||
#[dependencies.brotli]
|
#[dependencies.brotli]
|
||||||
|
@ -39,11 +40,6 @@ version = "0.13.5"
|
||||||
#[dependencies.directories]
|
#[dependencies.directories]
|
||||||
#version = "4.0"
|
#version = "4.0"
|
||||||
|
|
||||||
# logging backend
|
|
||||||
[dependencies.fern]
|
|
||||||
version = "0.6.1"
|
|
||||||
features = ["colored"]
|
|
||||||
|
|
||||||
# text rendering
|
# text rendering
|
||||||
#[dependencies.fontdue]
|
#[dependencies.fontdue]
|
||||||
#version = "0.7.2"
|
#version = "0.7.2"
|
||||||
|
@ -85,12 +81,6 @@ features = ["std"]
|
||||||
#[dependencies.serde]
|
#[dependencies.serde]
|
||||||
#version = "1.0"
|
#version = "1.0"
|
||||||
|
|
||||||
# async runtime
|
|
||||||
[dependencies.tokio]
|
|
||||||
version = "1.19"
|
|
||||||
# TODO: Is rt-multi-thread faster for our use case?
|
|
||||||
features = ["rt", "macros"]
|
|
||||||
|
|
||||||
# TOML (configuration format)
|
# TOML (configuration format)
|
||||||
#[dependencies.toml_edit]
|
#[dependencies.toml_edit]
|
||||||
#version = "0.14.4"
|
#version = "0.14.4"
|
||||||
|
@ -104,7 +94,26 @@ version = "0.13.1"
|
||||||
version = "0.26.1"
|
version = "0.26.1"
|
||||||
features = ["x11", "wayland"]
|
features = ["x11", "wayland"]
|
||||||
|
|
||||||
|
# logging backend
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.fern]
|
||||||
|
version = "0.6.1"
|
||||||
|
features = ["colored"]
|
||||||
|
|
||||||
|
# async runtime
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||||
|
version = "1.19"
|
||||||
|
# TODO: Is rt-multi-thread faster for our use case?
|
||||||
|
features = ["rt"]
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
console_log = "0.2.0"
|
||||||
|
console_error_panic_hook = "0.1.7"
|
||||||
|
wasm-bindgen = "0.2.81"
|
||||||
|
wasm-bindgen-futures = "0.4.31"
|
||||||
|
web-sys = { version = "0.3.58", features = ["HtmlCanvasElement"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
opt-level = "s"
|
||||||
|
|
|
@ -159,7 +159,7 @@ impl Graphics {
|
||||||
VertexBufferLayout {
|
VertexBufferLayout {
|
||||||
array_stride: std::mem::size_of::<Vertex>() as BufferAddress,
|
array_stride: std::mem::size_of::<Vertex>() as BufferAddress,
|
||||||
step_mode: VertexStepMode::Vertex,
|
step_mode: VertexStepMode::Vertex,
|
||||||
attributes: &vertex_attr_array![0 => Float32x3, 1 => Float32x3]
|
attributes: &vertex_attr_array![0 => Float32x2]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -224,7 +224,7 @@ impl Graphics {
|
||||||
format: self.surface_format,
|
format: self.surface_format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: PresentMode::Mailbox
|
present_mode: PresentMode::AutoVsync
|
||||||
});
|
});
|
||||||
self.uniform_copy_buffer.slice(..).get_mapped_range_mut().copy_from_slice(bytemuck::cast_slice(&[Uniforms {
|
self.uniform_copy_buffer.slice(..).get_mapped_range_mut().copy_from_slice(bytemuck::cast_slice(&[Uniforms {
|
||||||
dimensions: [self.desired_size.width as f32, self.desired_size.height as f32],
|
dimensions: [self.desired_size.width as f32, self.desired_size.height as f32],
|
||||||
|
@ -235,7 +235,11 @@ impl Graphics {
|
||||||
let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor::default());
|
let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor::default());
|
||||||
encoder.copy_buffer_to_buffer(&self.uniform_copy_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as u64);
|
encoder.copy_buffer_to_buffer(&self.uniform_copy_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as u64);
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
self.uniform_copy_buffer.slice(..).map_async(MapMode::Write, |err| err.unwrap());
|
self.uniform_copy_buffer.slice(..).map_async(MapMode::Write, |err| {
|
||||||
|
if let Err(err) = err {
|
||||||
|
log::error!("buffer async error: {}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reconfigure_surface_if_stale(&mut self) {
|
fn reconfigure_surface_if_stale(&mut self) {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
mod audio;
|
||||||
|
mod graphics;
|
||||||
|
|
||||||
|
use winit::window::WindowBuilder;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
|
pub fn main(canvas: web_sys::HtmlCanvasElement) {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
wasm_bindgen_futures::spawn_local(_main(move |wb| {
|
||||||
|
log::info!("callback");
|
||||||
|
use winit::platform::web::WindowBuilderExtWebSys;
|
||||||
|
wb.with_canvas(Some(canvas))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn _main<F>(wb_platform_specific: F)
|
||||||
|
where F: FnOnce(WindowBuilder) -> WindowBuilder
|
||||||
|
{
|
||||||
|
setup_logger();
|
||||||
|
log::info!("main");
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
// TODO: class and app id on unix
|
||||||
|
//use winit::platform::unix::WindowBuilderExtUnix;
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
|
let window = wb_platform_specific(WindowBuilder::new())
|
||||||
|
// Arbitrarily chosen as the minimum resolution the game is designed to support (for e.g. UI scaling).
|
||||||
|
.with_min_inner_size(winit::dpi::LogicalSize { height: 360, width: 640 })
|
||||||
|
.with_title("Pathland")
|
||||||
|
.with_maximized(true)
|
||||||
|
// TODO: hide window until first frame is drawn (default behavior on wayland)
|
||||||
|
.with_visible(true)
|
||||||
|
.with_decorations(true)
|
||||||
|
.build(&event_loop)
|
||||||
|
.expect("Failed to create window.");
|
||||||
|
// TODO: window icon, fullscreen, IME position, cursor grab, cursor visibility
|
||||||
|
let mut graphics = graphics::Graphics::setup(window).await;
|
||||||
|
//let audio = audio::Audio::setup();
|
||||||
|
|
||||||
|
event_loop.run(move |event, target, control_flow| {
|
||||||
|
use winit::event::*;
|
||||||
|
*control_flow = winit::event_loop::ControlFlow::Wait;
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { window_id, event } => {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
std::process::exit(0);
|
||||||
|
},
|
||||||
|
WindowEvent::Destroyed => {
|
||||||
|
std::process::exit(0);
|
||||||
|
},
|
||||||
|
WindowEvent::Focused(focused) => {
|
||||||
|
// TODO: handle focus/unfocus (e.g. pause, resume)
|
||||||
|
},
|
||||||
|
WindowEvent::Resized(new_size) => {
|
||||||
|
graphics.window_resized(new_size)
|
||||||
|
},
|
||||||
|
WindowEvent::ScaleFactorChanged { new_inner_size: new_size, .. } => {
|
||||||
|
graphics.window_resized(*new_size)
|
||||||
|
},
|
||||||
|
// TODO: handle user input
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::DeviceEvent { device_id, event } => {
|
||||||
|
// TODO: handle user input
|
||||||
|
},
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
// TODO: main event loop. queue simulation calculations, screen redrawing, etc.
|
||||||
|
},
|
||||||
|
Event::RedrawRequested(_) => {
|
||||||
|
graphics.draw();
|
||||||
|
},
|
||||||
|
Event::LoopDestroyed => {
|
||||||
|
std::process::exit(0);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// TODO: What is suspending/resuming? Do I want to support it?
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn setup_logger() {
|
||||||
|
use fern::Dispatch;
|
||||||
|
use fern::colors::ColoredLevelConfig;
|
||||||
|
use log::LevelFilter;
|
||||||
|
|
||||||
|
Dispatch::new()
|
||||||
|
.chain(
|
||||||
|
Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{}] {}",
|
||||||
|
ColoredLevelConfig::default().color(record.level()),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
})
|
||||||
|
.level(LevelFilter::Warn)
|
||||||
|
.level_for("pathland", LevelFilter::Info)
|
||||||
|
.chain(std::io::stderr()))
|
||||||
|
.chain(
|
||||||
|
fern::Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{}] {}",
|
||||||
|
record.level(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.level(LevelFilter::Debug)
|
||||||
|
.level_for("pathland", LevelFilter::Trace)
|
||||||
|
// FIXME: linux-specific path
|
||||||
|
.chain(std::fs::OpenOptions::new().write(true).create(true).truncate(true).open("/tmp/pathland.log").unwrap()))
|
||||||
|
.apply().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn setup_logger() {
|
||||||
|
console_log::init().unwrap();
|
||||||
|
}
|
108
src/main.rs
108
src/main.rs
|
@ -1,107 +1,19 @@
|
||||||
mod audio;
|
use winit::window::WindowBuilder;
|
||||||
mod graphics;
|
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
fn main() {
|
||||||
|
tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(pathland::_main(wb_platform_specific));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[cfg(unix)]
|
||||||
async fn main() {
|
fn wb_platform_specific(wb: WindowBuilder) -> WindowBuilder {
|
||||||
setup_logger();
|
|
||||||
|
|
||||||
use winit::event_loop::EventLoop;
|
|
||||||
use winit::platform::unix::WindowBuilderExtUnix;
|
use winit::platform::unix::WindowBuilderExtUnix;
|
||||||
let event_loop = EventLoop::new();
|
wb
|
||||||
|
|
||||||
let now = std::time::Instant::now();
|
|
||||||
|
|
||||||
let window = winit::window::WindowBuilder::new()
|
|
||||||
// Arbitrarily chosen as the minimum resolution the game is designed to support (for e.g. UI scaling).
|
|
||||||
.with_min_inner_size(winit::dpi::LogicalSize { height: 360, width: 640 })
|
|
||||||
.with_title("Pathland")
|
|
||||||
.with_maximized(true)
|
|
||||||
// TODO: hide window until first frame is drawn (default behavior on wayland)
|
|
||||||
.with_visible(true)
|
|
||||||
.with_decorations(true)
|
|
||||||
.with_class("pathland".to_string(), "pathland".to_string())
|
.with_class("pathland".to_string(), "pathland".to_string())
|
||||||
.with_app_id("pathland".to_string())
|
.with_app_id("pathland".to_string())
|
||||||
.build(&event_loop)
|
|
||||||
.expect("Failed to create window.");
|
|
||||||
// TODO: window icon, fullscreen, IME position, cursor grab, cursor visibility
|
|
||||||
let mut graphics = graphics::Graphics::setup(window).await;
|
|
||||||
//let audio = audio::Audio::setup();
|
|
||||||
log::info!("Took {} milliseconds", now.elapsed().as_millis());
|
|
||||||
|
|
||||||
event_loop.run(move |event, target, control_flow| {
|
|
||||||
use winit::event::*;
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Wait;
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { window_id, event } => {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
std::process::exit(0);
|
|
||||||
},
|
|
||||||
WindowEvent::Destroyed => {
|
|
||||||
std::process::exit(0);
|
|
||||||
},
|
|
||||||
WindowEvent::Focused(focused) => {
|
|
||||||
// TODO: handle focus/unfocus (e.g. pause, resume)
|
|
||||||
},
|
|
||||||
WindowEvent::Resized(new_size) => {
|
|
||||||
graphics.window_resized(new_size)
|
|
||||||
},
|
|
||||||
WindowEvent::ScaleFactorChanged { new_inner_size: new_size, .. } => {
|
|
||||||
graphics.window_resized(*new_size)
|
|
||||||
},
|
|
||||||
// TODO: handle user input
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::DeviceEvent { device_id, event } => {
|
|
||||||
// TODO: handle user input
|
|
||||||
},
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
// TODO: main event loop. queue simulation calculations, screen redrawing, etc.
|
|
||||||
},
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
graphics.draw();
|
|
||||||
},
|
|
||||||
Event::LoopDestroyed => {
|
|
||||||
std::process::exit(0);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// TODO: What is suspending/resuming? Do I want to support it?
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_logger() {
|
#[cfg(not(unix))]
|
||||||
use fern::Dispatch;
|
fn wb_platform_specific(wb: WindowBuilder) -> WindowBuilder {
|
||||||
use fern::colors::ColoredLevelConfig;
|
wb
|
||||||
use log::LevelFilter;
|
|
||||||
|
|
||||||
Dispatch::new()
|
|
||||||
.chain(
|
|
||||||
Dispatch::new()
|
|
||||||
.format(|out, message, record| {
|
|
||||||
out.finish(format_args!(
|
|
||||||
"[{}] {}",
|
|
||||||
ColoredLevelConfig::default().color(record.level()),
|
|
||||||
message
|
|
||||||
));
|
|
||||||
})
|
|
||||||
.level(LevelFilter::Warn)
|
|
||||||
.level_for("pathland", LevelFilter::Info)
|
|
||||||
.chain(std::io::stderr()))
|
|
||||||
.chain(
|
|
||||||
fern::Dispatch::new()
|
|
||||||
.format(|out, message, record| {
|
|
||||||
out.finish(format_args!(
|
|
||||||
"[{}] {}",
|
|
||||||
record.level(),
|
|
||||||
message
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.level(LevelFilter::Debug)
|
|
||||||
.level_for("pathland", LevelFilter::Trace)
|
|
||||||
.chain(std::fs::OpenOptions::new().write(true).create(true).truncate(true).open("/tmp/pathland.log").unwrap()))
|
|
||||||
.apply().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// A dependency graph that contains any wasm must all be imported
|
||||||
|
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||||
|
// that no one else needs to worry about it again.
|
||||||
|
import("./index.js")
|
||||||
|
.catch(e => console.error("Error importing `index.js`:", e));
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>pathland</title>
|
||||||
|
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<script src="bootstrap.js"></script>
|
|
@ -0,0 +1,4 @@
|
||||||
|
import * as pathland from "pathland";
|
||||||
|
|
||||||
|
let canvas = document.getElementById("canvas");
|
||||||
|
pathland.main(document.getElementById("canvas"));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "pathland",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"start": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jamestmartin/pathland"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"webassembly",
|
||||||
|
"wasm",
|
||||||
|
"rust",
|
||||||
|
"webpack"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"pathland": "file:../pkg"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"webpack": "^5.73.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-dev-server": "^4.9.3",
|
||||||
|
"copy-webpack-plugin": "^5.1.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./bootstrap.js",
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "bootstrap.js",
|
||||||
|
hashFunction: "xxhash64",
|
||||||
|
},
|
||||||
|
mode: "development",
|
||||||
|
plugins: [
|
||||||
|
new CopyWebpackPlugin(['index.html'])
|
||||||
|
],
|
||||||
|
experiments: {
|
||||||
|
syncWebAssembly: true
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue