diff --git a/.cargo/config.toml b/.cargo/config.toml index e3ce5567a..78c19d933 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -14,16 +14,6 @@ incremental = true [cache] auto-clean-frequency = "1 day" -[profile.dev] -incremental = true -debug = true - -[profile.release] -incremental = true -lto = "thin" -codegen-units = 1 -strip = true - [target.x86_64-pc-windows-msvc] linker = "rust-lld" rustflags = [ @@ -87,9 +77,20 @@ rustflags = [ [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" +[target.wasm32-unknown-unknown] +rustflags = [ + "--cfg", + "tokio_unstable", + "--cfg", + 'getrandom_backend="wasm_js"', + "-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals", + "-Aunused", +] + [unstable.git] shallow_index = true shallow_deps = true + [unstable.gitoxide] fetch = true checkout = true diff --git a/.github/workflows/pack-ci.yml b/.github/workflows/pack-ci.yml index c832bec11..e2ce6f5ae 100644 --- a/.github/workflows/pack-ci.yml +++ b/.github/workflows/pack-ci.yml @@ -88,7 +88,7 @@ jobs: uses: dtolnay/rust-toolchain@stable if: ${{ !matrix.settings.docker }} with: - toolchain: stable + toolchain: nightly-2025-06-04 targets: ${{ matrix.settings.target }} - name: Cache cargo uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/pack-release.yml b/.github/workflows/pack-release.yml index b3bd212a4..f667374df 100644 --- a/.github/workflows/pack-release.yml +++ b/.github/workflows/pack-release.yml @@ -94,7 +94,7 @@ jobs: uses: dtolnay/rust-toolchain@stable if: ${{ !matrix.settings.docker }} with: - toolchain: stable + toolchain: nightly-2025-06-04 targets: ${{ matrix.settings.target }} - name: Cache cargo uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/utooweb-ci.yml b/.github/workflows/utooweb-ci.yml new file mode 100644 index 000000000..20e571999 --- /dev/null +++ b/.github/workflows/utooweb-ci.yml @@ -0,0 +1,43 @@ +name: utooweb-ci + +env: + APP_NAME: "@utoo/web" + +on: [pull_request] + +jobs: + build: + name: build-wasm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submoduls: false + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: | + ${{ secrets.CI_SUBMODULE }} + - name: Init git submodules + run: git submodule update --init + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: | + npm install + npm install binaryen@123.0.0 --global + cargo install -f wasm-bindgen-cli --version 0.2.100 + rustup target add wasm32-unknown-unknown + rustup component add rust-src --toolchain nightly-2025-06-04-x86_64-unknown-linux-gnu + - name: Install rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2025-06-04 + - name: Build WASM + run: | + npm run build:local --workspace=@utoo/web + ls packages/utoo-web/esm/utoo + diff --git a/.github/workflows/utooweb-release.yml b/.github/workflows/utooweb-release.yml new file mode 100644 index 000000000..668ed5e38 --- /dev/null +++ b/.github/workflows/utooweb-release.yml @@ -0,0 +1,56 @@ +name: utooweb-release + +env: + APP_NAME: "@utoo/web" + +on: + release: + types: [published] + +jobs: + publish: + if: startsWith(github.event.release.tag_name, 'utooweb-v') + name: Publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submoduls: false + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: | + ${{ secrets.CI_SUBMODULE }} + - name: Init git submodules + run: git submodule update --init + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: | + npm install + npm install binaryen@123.0.0 --global + cargo install -f wasm-bindgen-cli --version 0.2.100 + rustup target add wasm32-unknown-unknown + rustup component add rust-src --toolchain nightly-2025-06-04-x86_64-unknown-linux-gnu + - name: Install rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2025-06-04 + - name: Get version from release or use alpha version + id: get_version + run: | + echo "VERSION=${GITHUB_REF#refs/tags/utooweb-v}" >> $GITHUB_OUTPUT + - name: NPM Publish + run: | + # npm config set provenance true + echo "${VERSION}" && + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && + + npm version --workspace=@utoo/web "${VERSION}" && + npm publish --workspace=@utoo/web --access public + env: + VERSION: ${{ steps.get_version.outputs.VERSION }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.vscode/setting.json b/.vscode/setting.json deleted file mode 100644 index f6e236ee6..000000000 --- a/.vscode/setting.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rust-analyzer.linkedProjects": [ - "./Cargo.toml", - "./next.js/Cargo.toml" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..462a5c057 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.defaultFormatter": "biomejs.biome" +} diff --git a/Cargo.lock b/Cargo.lock index b8de14dba..39e976dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1021,6 +1021,16 @@ dependencies = [ "tracing-subscriber", ] +[[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", + "wasm-bindgen", +] + [[package]] name = "const-str" version = "0.3.2" @@ -2723,9 +2733,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.0", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -4070,6 +4082,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opfs-project" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3058e7beaf30f8fe0ea45bc5a057b498ba0583680ea07ecab86a9c91c3befe" +dependencies = [ + "flate2", + "futures", + "futures-util", + "reqwest 0.12.22", + "serde", + "serde_json", + "tar", + "tokio-fs-ext", +] + [[package]] name = "outref" version = "0.1.0" @@ -4127,7 +4155,6 @@ dependencies = [ "turbopack-node", "turbopack-nodejs", "turbopack-static", - "turbopack-trace-utils", "url", "urlencoding", "vergen", @@ -4170,6 +4197,7 @@ dependencies = [ "base64 0.22.1", "dunce", "either", + "getrandom 0.3.3", "indoc", "mime_guess", "modularize_imports", @@ -4201,7 +4229,6 @@ dependencies = [ "turbopack-node", "turbopack-resolve", "turbopack-static", - "turbopack-trace-utils", "urlencoding", ] @@ -5297,7 +5324,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-util", @@ -5317,7 +5344,9 @@ checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", + "h2 0.4.11", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -5327,6 +5356,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -7903,7 +7933,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -7916,6 +7957,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -8163,8 +8214,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +source = "git+https://github.com/utooland/tokio.git#0b9f7720e2b65634e0fb7d7d8be9073fc21d96a1" dependencies = [ "backtrace", "bytes", @@ -8178,6 +8228,8 @@ dependencies = [ "socket2 0.6.0", "tokio-macros", "tracing", + "wasm_thread", + "wasmtimer", "windows-sys 0.59.0", ] @@ -8204,8 +8256,7 @@ dependencies = [ [[package]] name = "tokio-macros" version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +source = "git+https://github.com/utooland/tokio.git#0b9f7720e2b65634e0fb7d7d8be9073fc21d96a1" dependencies = [ "proc-macro2", "quote", @@ -8285,8 +8336,7 @@ dependencies = [ [[package]] name = "tokio-util" version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +source = "git+https://github.com/utooland/tokio.git#0b9f7720e2b65634e0fb7d7d8be9073fc21d96a1" dependencies = [ "bytes", "futures-core", @@ -8527,6 +8577,19 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-web" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e6a141feebd51f8d91ebfd785af50fca223c570b86852166caa3b141defe7c" +dependencies = [ + "js-sys", + "tracing-core", + "tracing-subscriber", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "triomphe" version = "0.1.12" @@ -9557,6 +9620,41 @@ dependencies = [ "toml 0.8.23", ] +[[package]] +name = "utoo-wasm" +version = "0.1.0" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "flate2", + "futures", + "js-sys", + "opfs-project", + "pack-api", + "pack-core", + "parking_lot", + "petgraph 0.6.5", + "reqwest 0.12.22", + "rustc-hash 2.1.1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tar", + "tokio", + "tokio-fs-ext", + "tracing", + "tracing-subscriber", + "tracing-web", + "turbo-rcstr", + "turbo-tasks", + "turbo-tasks-backend", + "turbo-tasks-build", + "turbo-tasks-fs", + "turbopack-core", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "uuid" version = "1.17.0" @@ -9894,6 +9992,17 @@ dependencies = [ "wasmparser 0.235.0", ] +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "git+https://github.com/utooland/wasm_thread.git#1155d645aedfdbe55f8ea336a9e8a8150239beb0" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasmer" version = "6.1.0-rc.2" @@ -10235,6 +10344,20 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "wast" version = "235.0.0" @@ -10398,6 +10521,17 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index f0feea025..844d47b4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/pack-schema", "crates/pack-tests", "crates/pm", + "crates/utoo-wasm", ] exclude = ["crates/runtime", "next.js"] resolver = "2" @@ -18,9 +19,10 @@ license = "MIT" [workspace.dependencies] pack-core = { path = "./crates/pack-core" } pack-api = { path = "./crates/pack-api" } + schemars = "0.8" futures = "0.3.31" -tokio = { version = "1.43.0", features = ["full"] } +tokio = { version = "1.47.1" } thiserror = "1.0" anyhow = "1.0.96" tracing = "0.1.41" @@ -41,6 +43,7 @@ criterion = "0.6.0" dirs = "5.0" toml = "0.8" async-trait = "0.1.64" +rayon = "1.10.0" # SWC crates swc_core = { version = "36.0.1", features = [ @@ -57,7 +60,7 @@ url = "2.2.2" urlencoding = "2.1.2" qstring = "0.7.2" indoc = "2.0.0" -parking_lot = "0.12.1" +parking_lot = "0.12.4" # Dependencies from nextjs, should keep the same rev! ## Dependencies for common cache and task. See https://github.com/utooland/next.js/blob/canary/turbopack/crates/turbopack/architecture.md @@ -82,7 +85,7 @@ turbopack-browser = { path = "./next.js/turbopack/crates/turbopack-browser" } turbopack-core = { path = "./next.js/turbopack/crates/turbopack-core" } turbopack-resolve = { path = "./next.js/turbopack/crates/turbopack-resolve" } turbopack-ecmascript = { path = "./next.js/turbopack/crates/turbopack-ecmascript" } -turbopack-ecmascript-plugins = { path = "./next.js/turbopack/crates/turbopack-ecmascript-plugins" } +turbopack-ecmascript-plugins = { path = "./next.js/turbopack/crates/turbopack-ecmascript-plugins", default-features = false } turbopack-ecmascript-runtime = { path = "./next.js/turbopack/crates/turbopack-ecmascript-runtime" } turbopack-ecmascript-hmr-protocol = { path = "./next.js/turbopack/crates/turbopack-ecmascript-hmr-protocol" } turbopack-image = { path = "./next.js/turbopack/crates/turbopack-image" } @@ -97,7 +100,7 @@ turbopack-test-utils = { path = "./next.js/turbopack/crates/turbopack-test-utils too_many_arguments = "allow" [profile.release] -opt-level = 3 +opt-level = "z" lto = "fat" strip = "symbols" @@ -120,4 +123,17 @@ lto = false strip = "none" incremental = true codegen-units = 256 -debug = 2 +debug = 1 + +[profile.wasm-dev] +inherits = "release" +opt-level = 1 +lto = false +strip = "none" +incremental = true +codegen-units = 256 +debug = 1 + +[patch.crates-io] +tokio = { git = "https://github.com/utooland/tokio.git", package = "tokio" } +tokio-util = { git = "https://github.com/utooland/tokio.git", package = "tokio-util" } diff --git a/biome.json b/biome.json index 4143f8ef5..5986d768b 100644 --- a/biome.json +++ b/biome.json @@ -9,10 +9,13 @@ "ignoreUnknown": false, "includes": [ "**/package.json", - "**/packages/**/*.ts", + "**/packages/**/src/*", + "**/examples/**/src/*", + "**/examples/**/webpack.config.js", "./packages/pack/docs/features-list.json", "crates/pack-core/js/**/*.ts", - "!**/packages/**/binding.d.ts", + "!**/examples/multi-entries-heavy/*", + "!**/packages/**/binding.*", "!next.js/**" ] }, diff --git a/crates/pack-api/Cargo.toml b/crates/pack-api/Cargo.toml index a097ecaf9..e7c871c94 100644 --- a/crates/pack-api/Cargo.toml +++ b/crates/pack-api/Cargo.toml @@ -6,17 +6,18 @@ license.workspace = true [dependencies] anyhow = { workspace = true, features = ["backtrace"] } -pack-core = { workspace = true } qstring = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tracing = { workspace = true } -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["sync"] } url = { workspace = true } futures = { workspace = true } urlencoding = { workspace = true } parking_lot = { workspace = true } + +## turbpack specific turbo-rcstr = { workspace = true } turbo-tasks = { workspace = true } turbo-tasks-env = { workspace = true } @@ -29,7 +30,7 @@ turbopack-core = { workspace = true } turbopack-node = { workspace = true } turbopack-nodejs = { workspace = true } turbopack-static = { workspace = true } -turbopack-trace-utils = { workspace = true } +pack-core = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/pack-api/src/project.rs b/crates/pack-api/src/project.rs index e347b3495..2add1b567 100644 --- a/crates/pack-api/src/project.rs +++ b/crates/pack-api/src/project.rs @@ -24,9 +24,14 @@ use turbo_tasks::{ }; use turbo_tasks_env::{EnvMap, ProcessEnv}; use turbo_tasks_fs::{DiskFileSystem, FileSystem, FileSystemPath, VirtualFileSystem, invalidation}; -use turbopack::{ - evaluate_context::node_build_environment, global_module_ids::get_global_module_id_strategy, -}; +use turbopack::global_module_ids::get_global_module_id_strategy; + +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +use turbopack::evaluate_context::node_build_environment; + +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +use turbopack::evaluate_context::webworker_build_environment; + use turbopack_core::{ PROJECT_FILESYSTEM_NAME, changed::content_changed, @@ -51,7 +56,6 @@ use turbopack_core::{ }; use turbopack_node::execution_context::ExecutionContext; use turbopack_nodejs::NodeJsChunkingContext; -use turbopack_trace_utils::exit::ExitReceiver; use crate::{ app::{AppEntrypoint, AppProject, OptionAppProject}, @@ -693,28 +697,57 @@ impl Project { let node_root = self.node_root().owned().await?; let mode = self.mode().await?; - let node_execution_chunking_context = Vc::upcast( - NodeJsChunkingContext::builder( - self.project_root().owned().await?, - node_root.clone(), - self.node_root_to_root_path().owned().await?, - node_root.clone(), - node_root.clone(), - node_root.clone(), - node_build_environment().to_resolved().await?, - mode.runtime_type(), - ) - .source_maps(if *self.config().source_maps().await? { - SourceMapsType::Full - } else { - SourceMapsType::None - }) - .build(), - ); + let project_root = self.project_root().owned().await?; + let node_root_to_root_path = self.node_root_to_root_path().owned().await?; + let source_maps = if *self.config().source_maps().await? { + SourceMapsType::Full + } else { + SourceMapsType::None + }; + + let execution_chunking_context = { + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + { + let build_environment = node_build_environment().to_resolved().await?; + Vc::upcast( + NodeJsChunkingContext::builder( + project_root, + node_root.clone(), + node_root_to_root_path, + node_root.clone(), + node_root.clone(), + node_root.clone(), + build_environment, + mode.runtime_type(), + ) + .source_maps(source_maps) + .build(), + ) + } + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + { + use turbopack_browser::BrowserChunkingContext; + let build_environment = webworker_build_environment().to_resolved().await?; + Vc::upcast( + BrowserChunkingContext::builder( + project_root, + node_root.clone(), + node_root_to_root_path, + node_root.clone(), + node_root.clone(), + node_root.clone(), + build_environment, + mode.runtime_type(), + ) + .source_maps(source_maps) + .build(), + ) + } + }; Ok(ExecutionContext::new( self.project_path().owned().await?, - node_execution_chunking_context, + execution_chunking_context, self.env(), )) } @@ -1231,12 +1264,6 @@ async fn copy_output_assets_operation(project: ResolvedVc) -> Result, - pub exit_receiver: tokio::sync::Mutex>, -} - fn clean_directory(dist_path: &Path) -> Result<()> { let canonical_path = fs::canonicalize(dist_path) .with_context(|| format!("Failed to canonicalize path: {}", dist_path.display()))?; diff --git a/crates/pack-api/src/tasks.rs b/crates/pack-api/src/tasks.rs index 3e63a2eeb..eadc5d5ad 100644 --- a/crates/pack-api/src/tasks.rs +++ b/crates/pack-api/src/tasks.rs @@ -5,11 +5,16 @@ use turbo_tasks::{ TaskId, TurboTasks, TurboTasksApi, UpdateInfo, Vc, task_statistics::TaskStatisticsApi, trace::TraceRawVcs, }; -use turbo_tasks_backend::{DefaultBackingStorage, NoopBackingStorage, TurboTasksBackend}; + +use turbo_tasks_backend::{NoopBackingStorage, TurboTasksBackend}; + +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +use turbo_tasks_backend::DefaultBackingStorage; #[derive(Clone)] pub enum BundlerTurboTasks { Memory(Arc>>), + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] PersistentCaching(Arc>>), } @@ -17,6 +22,7 @@ impl BundlerTurboTasks { pub fn dispose_root_task(&self, task: TaskId) { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.dispose_root_task(task), + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => { turbo_tasks.dispose_root_task(task) } @@ -31,6 +37,7 @@ impl BundlerTurboTasks { { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.spawn_root_task(functor), + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => { turbo_tasks.spawn_root_task(functor) } @@ -43,6 +50,7 @@ impl BundlerTurboTasks { ) -> Result { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.run_once(future).await, + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => turbo_tasks.run_once(future).await, } } @@ -54,6 +62,7 @@ impl BundlerTurboTasks { { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.spawn_once_task(future), + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => { turbo_tasks.spawn_once_task(future) } @@ -71,6 +80,7 @@ impl BundlerTurboTasks { .aggregated_update_info(aggregation, timeout) .await } + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => { turbo_tasks .aggregated_update_info(aggregation, timeout) @@ -86,6 +96,7 @@ impl BundlerTurboTasks { .get_or_wait_aggregated_update_info(aggregation) .await } + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => { turbo_tasks .get_or_wait_aggregated_update_info(aggregation) @@ -97,6 +108,7 @@ impl BundlerTurboTasks { pub async fn stop_and_wait(&self) { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.stop_and_wait().await, + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => turbo_tasks.stop_and_wait().await, } } @@ -104,6 +116,7 @@ impl BundlerTurboTasks { pub fn task_statistics(&self) -> &TaskStatisticsApi { match self { BundlerTurboTasks::Memory(turbo_tasks) => turbo_tasks.task_statistics(), + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] BundlerTurboTasks::PersistentCaching(turbo_tasks) => turbo_tasks.task_statistics(), } } diff --git a/crates/pack-api/src/utils.rs b/crates/pack-api/src/utils.rs index d874447b3..063e7847f 100644 --- a/crates/pack-api/src/utils.rs +++ b/crates/pack-api/src/utils.rs @@ -1,12 +1,13 @@ use std::sync::Arc; use anyhow::Result; +use serde::Serialize; use turbo_tasks::{ Completion, Effects, OperationVc, ReadRef, TryJoinIterExt, Vc, VcValueType, get_effects, }; use turbopack_core::{ diagnostics::{Diagnostic, DiagnosticContextExt, PlainDiagnostic}, - issue::{IssueDescriptionExt, IssueSeverity, PlainIssue}, + issue::{IssueDescriptionExt, IssueSeverity, PlainIssue, StyledString}, }; use crate::endpoint::{Endpoint, EndpointIssuesAndDiags, endpoint_server_changed_operation}; @@ -95,3 +96,39 @@ pub async fn get_diagnostics( Ok(Arc::new(diags)) } + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum StyledStringSerialize<'a> { + Line { + value: Vec>, + }, + Stack { + value: Vec>, + }, + Text { + value: &'a str, + }, + Code { + value: &'a str, + }, + Strong { + value: &'a str, + }, +} + +impl<'a> From<&'a StyledString> for StyledStringSerialize<'a> { + fn from(value: &'a StyledString) -> Self { + match value { + StyledString::Line(parts) => StyledStringSerialize::Line { + value: parts.iter().map(|p| p.into()).collect(), + }, + StyledString::Stack(parts) => StyledStringSerialize::Stack { + value: parts.iter().map(|p| p.into()).collect(), + }, + StyledString::Text(string) => StyledStringSerialize::Text { value: string }, + StyledString::Code(string) => StyledStringSerialize::Code { value: string }, + StyledString::Strong(string) => StyledStringSerialize::Strong { value: string }, + } + } +} diff --git a/crates/pack-cli/Cargo.toml b/crates/pack-cli/Cargo.toml index 730e821bb..0bb51d5b4 100644 --- a/crates/pack-cli/Cargo.toml +++ b/crates/pack-cli/Cargo.toml @@ -28,10 +28,12 @@ turbo-tasks = { workspace = true } turbo-tasks-backend = { workspace = true } turbo-tasks-malloc = { workspace = true } turbopack-core = { workspace = true } -turbopack-trace-utils = { workspace = true } turbopack-cli-utils = { workspace = true } turbopack-dev-server = { workspace = true } +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +turbopack-trace-utils = { workspace = true } + [build-dependencies] turbo-tasks-build = { workspace = true } diff --git a/crates/pack-core/Cargo.toml b/crates/pack-core/Cargo.toml index 14e8c17fb..230ba2633 100644 --- a/crates/pack-core/Cargo.toml +++ b/crates/pack-core/Cargo.toml @@ -19,16 +19,16 @@ either = { workspace = true, features = ["serde"] } qstring = "0.7.2" serde = { workspace = true } serde_json = { workspace = true } +serde_path_to_error = { workspace = true } mime_guess = "2.0.4" indoc = { workspace = true } tracing = { workspace = true } dunce = { workspace = true } pathdiff = { workspace = true } rustc-hash = { workspace = true } -remove_console = { workspace = true } urlencoding = { workspace = true } -modularize_imports = { workspace = true } -serde_path_to_error = { workspace = true } + +## turbpack specific swc_core = { workspace = true, features = [ "base", "common_concurrent", @@ -46,28 +46,37 @@ swc_core = { workspace = true, features = [ "ecma_utils", "ecma_visit", ] } +modularize_imports = { workspace = true } +remove_console = { workspace = true } turbo-esregex = { workspace = true } turbo-rcstr = { workspace = true } turbo-tasks = { workspace = true } turbo-tasks-bytes = { workspace = true } turbo-tasks-env = { workspace = true } -turbo-tasks-fetch = { workspace = true } turbo-tasks-fs = { workspace = true } turbo-tasks-hash = { workspace = true } turbopack = { workspace = true } turbopack-browser = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } -turbopack-ecmascript-plugins = { workspace = true, features = [ - "transform_emotion", -] } turbopack-ecmascript-runtime = { workspace = true } turbopack-node = { workspace = true } turbopack-static = { workspace = true } turbopack-image = { workspace = true } turbopack-resolve = { workspace = true } -turbopack-trace-utils = { workspace = true } + +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +turbo-tasks-fetch = { workspace = true } +turbopack-ecmascript-plugins = { workspace = true, default-features = true, features = [ + "transform_emotion", +] } + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.3", features = ["wasm_js"] } +turbopack-ecmascript-plugins = { workspace = true, default-features = false, features = [ + "transform_emotion", +] } [build-dependencies] turbo-tasks-build = { workspace = true } diff --git a/crates/pack-core/js/src/umd/runtime-base.ts b/crates/pack-core/js/src/umd/runtime-base.ts index 071d80ce0..931c7413f 100644 --- a/crates/pack-core/js/src/umd/runtime-base.ts +++ b/crates/pack-core/js/src/umd/runtime-base.ts @@ -28,9 +28,10 @@ function normalizeChunkPath(path: string) { path = path.substring(2); } - if (path.endsWith("/")) { - path = path.slice(0, -1); + if (!path.endsWith("/")) { + path += "/"; } + return path; } diff --git a/crates/pack-core/src/client/context.rs b/crates/pack-core/src/client/context.rs index d341c1dda..f43ff47d2 100644 --- a/crates/pack-core/src/client/context.rs +++ b/crates/pack-core/src/client/context.rs @@ -133,8 +133,8 @@ pub async fn get_client_compile_time_info( let define_env = Vc::cell(define_env); let environment = BrowserEnvironment { dom: true, - web_worker: false, - service_worker: false, + web_worker: true, + service_worker: true, browserslist_query: browserslist_query.to_owned(), } .resolved_cell(); @@ -234,6 +234,7 @@ pub async fn get_client_module_options_context( // implicitly does by default. let mut foreign_conditions = loader_conditions.clone(); foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign); + let foreign_enable_webpack_loaders = *webpack_loader_options(project_path.clone(), config, foreign_conditions).await?; @@ -271,12 +272,23 @@ pub async fn get_client_module_options_context( client_rules.push(get_dynamic_import_to_require_rule()); } + let postcss_package = { + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + { + Some( + get_postcss_package_mapping(project_path.clone()) + .to_resolved() + .await?, + ) + } + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + { + None + } + }; + let postcss_transform_options = PostCssTransformOptions { - postcss_package: Some( - get_postcss_package_mapping(project_path.clone()) - .to_resolved() - .await?, - ), + postcss_package, config_location: PostCssConfigLocation::ProjectPathOrLocalPath, ..Default::default() }; diff --git a/crates/pack-core/src/import_map.rs b/crates/pack-core/src/import_map.rs index 870ac7b15..dc6c589cf 100644 --- a/crates/pack-core/src/import_map.rs +++ b/crates/pack-core/src/import_map.rs @@ -70,13 +70,7 @@ pub async fn get_client_import_map( ) -> Result> { let mut import_map = ImportMap::empty(); - insert_shared_aliases( - &mut import_map, - project_path.clone(), - execution_context, - config, - ) - .await?; + insert_shared_aliases(&mut import_map, &project_path, execution_context, config).await?; insert_alias_option( &mut import_map, @@ -92,12 +86,15 @@ pub async fn get_client_import_map( // Make sure to not add any external requests here. async fn insert_shared_aliases( import_map: &mut ImportMap, - project_path: FileSystemPath, + project_path: &FileSystemPath, _execution_context: Vc, _config: Vc, ) -> Result<()> { - let pack_package = get_utoopack_path(project_path.clone()).owned().await?; - import_map.insert_singleton_alias("@swc/helpers", pack_package.clone()); + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] + { + let pack_package = get_utoopack_path(project_path.clone()).owned().await?; + import_map.insert_singleton_alias("@swc/helpers", pack_package.clone()); + } // FIXME: maybe we don't need this // import_map.insert_singleton_alias("styled-jsx", pack_package.clone()); // import_map.insert_singleton_alias("react", project_path.clone()); diff --git a/crates/pack-core/src/lib.rs b/crates/pack-core/src/lib.rs index 321e5b175..dec703989 100644 --- a/crates/pack-core/src/lib.rs +++ b/crates/pack-core/src/lib.rs @@ -22,6 +22,7 @@ pub fn register() { turbo_tasks::register(); turbo_tasks_bytes::register(); turbo_tasks_fs::register(); + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] turbo_tasks_fetch::register(); turbopack_browser::register(); turbopack_node::register(); diff --git a/crates/pack-core/src/shared/webpack_rules/less.rs b/crates/pack-core/src/shared/webpack_rules/less.rs index 4e2c1f595..513f369f4 100644 --- a/crates/pack-core/src/shared/webpack_rules/less.rs +++ b/crates/pack-core/src/shared/webpack_rules/less.rs @@ -27,9 +27,12 @@ pub async fn get_less_loader_rules( .or(Some(&empty_additional_data))); let less_loader = WebpackLoaderItem { + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] loader: get_utoopack_dependency_package(project_path.clone(), rcstr!("less-loader")) .owned() .await?, + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + loader: rcstr!("less-loader"), options: take( serde_json::json!({ "implementation": less_options.get("implementation"), diff --git a/crates/pack-core/src/shared/webpack_rules/mod.rs b/crates/pack-core/src/shared/webpack_rules/mod.rs index 41fcb571f..f08bcc8b2 100644 --- a/crates/pack-core/src/shared/webpack_rules/mod.rs +++ b/crates/pack-core/src/shared/webpack_rules/mod.rs @@ -152,11 +152,14 @@ pub async fn webpack_loader_options( WebpackLoadersOptions { rules: ResolvedVc::cell(rules), conditions, + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] loader_runner_package: Some( loader_runner_package_mapping(project_path) .to_resolved() .await?, ), + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + loader_runner_package: None, builtin_conditions: UtooWebpackLoaderBuiltinConditionSet::new(builtin_conditions) .to_resolved() .await?, diff --git a/crates/pack-core/src/shared/webpack_rules/sass.rs b/crates/pack-core/src/shared/webpack_rules/sass.rs index cf037bc38..8d6817c10 100644 --- a/crates/pack-core/src/shared/webpack_rules/sass.rs +++ b/crates/pack-core/src/shared/webpack_rules/sass.rs @@ -36,9 +36,12 @@ pub async fn get_sass_loader_rules( .or(Some(&empty_additional_data)); let sass_loader = WebpackLoaderItem { + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] loader: get_utoopack_dependency_package(project_path.clone(), rcstr!("sass-loader")) .owned() .await?, + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + loader: rcstr!("sass-loader"), options: take( serde_json::json!({ "implementation": sass_options.get("implementation"), @@ -51,9 +54,12 @@ pub async fn get_sass_loader_rules( ), }; let resolve_url_loader = WebpackLoaderItem { + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] loader: get_utoopack_dependency_package(project_path.clone(), rcstr!("resolve-url-loader")) .owned() .await?, + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + loader: rcstr!("resolve-url-loader"), options: take( serde_json::json!({ //https://github.com/vercel/turbo/blob/d527eb54be384a4658243304cecd547d09c05c6b/crates/turbopack-node/src/transforms/webpack.rs#L191 diff --git a/crates/pack-core/src/tracing_presets.rs b/crates/pack-core/src/tracing_presets.rs index 95f5fa2de..097543528 100644 --- a/crates/pack-core/src/tracing_presets.rs +++ b/crates/pack-core/src/tracing_presets.rs @@ -1,22 +1,37 @@ use std::sync::LazyLock; -use turbopack_trace_utils::tracing_presets::{ - TRACING_OVERVIEW_TARGETS as _TRACING_OVERVIEW_TARGETS, - TRACING_TURBO_TASKS_TARGETS as _TRACING_TURBO_TASKS_TARGETS, - TRACING_TURBOPACK_TARGETS as _TRACING_TURBOPACK_TARGETS, -}; pub static TRACING_OVERVIEW_TARGETS: LazyLock> = LazyLock::new(|| { - [ - &_TRACING_OVERVIEW_TARGETS[..], - &[ - "pack_napi=info", - "pack=info", - "pack_api=info", - "pack_core=info", - "turbopack_node=info", - ], + vec![ + "turbo_tasks=info", + "turbo_tasks_fs=info", + "turbo_tasks_fetch=info", + "turbopack=info", + "turbopack_binding=info", + "turbopack_browser=info", + "turbopack_nodejs=info", + "turbopack_cli=info", + "turbopack_cli_utils=info", + "turbopack_core=info", + "turbopack_css=info", + "turbopack_dev_server=info", + "turbopack_ecmascript=info", + "turbopack_ecmascript_hmr_protocol=info", + "turbopack_ecmascript_plugins=info", + "turbopack_ecmascript_runtime=info", + "turbopack_env=info", + "turbopack_image=info", + "turbopack_json=info", + "turbopack_mdx=info", + "turbopack_node=info", + "turbopack_static=info", + "turbopack_swc_utils=info", + "turbopack_wasm=info", + "turbopack_node=info", + "pack_core=info", + "pack_api=info", + "pack_napi=info", + "pack=info", ] - .concat() }); pub static TRACING_TARGETS: LazyLock> = LazyLock::new(|| { @@ -31,12 +46,51 @@ pub static TRACING_TARGETS: LazyLock> = LazyLock::new(|| { ] .concat() }); -pub static TRACING_TURBOPACK_TARGETS: LazyLock> = - LazyLock::new(|| [&TRACING_TARGETS[..], &_TRACING_TURBOPACK_TARGETS[..]].concat()); +pub static TRACING_TURBOPACK_TARGETS: LazyLock> = LazyLock::new(|| { + [ + &TRACING_TARGETS[..], + &[ + "turbopack=trace", + "turbopack_binding=trace", + "turbopack_nodejs=trace", + "turbopack_cli=trace", + "turbopack_cli_utils=trace", + "turbopack_core=trace", + "turbopack_css=trace", + "turbopack_browser=trace", + "turbopack_dev_server=trace", + "turbopack_ecmascript=trace", + "turbopack_ecmascript_hmr_protocol=trace", + "turbopack_ecmascript_plugins=trace", + "turbopack_ecmascript_runtime=trace", + "turbopack_env=trace", + "turbopack_image=trace", + "turbopack_json=trace", + "turbopack_mdx=trace", + "turbopack_node=trace", + "turbopack_static=trace", + "turbopack_swc_utils=trace", + "turbopack_wasm=trace", + "swc_ecma_minifier=trace", + ], + ] + .concat() +}); pub static TRACING_TURBO_TASKS_TARGETS: LazyLock> = LazyLock::new(|| { [ &TRACING_TURBOPACK_TARGETS[..], - &_TRACING_TURBO_TASKS_TARGETS[..], + &[ + "turbo_tasks=trace", + "turbo_tasks_auto_hash_map=trace", + "turbo_tasks_build=trace", + "turbo_tasks_bytes=trace", + "turbo_tasks_env=trace", + "turbo_tasks_fetch=trace", + "turbo_tasks_fs=trace", + "turbo_tasks_hash=trace", + "turbo_tasks_backend=trace", + "turbo_persistence=trace", + ], ] .concat() }); diff --git a/crates/pack-napi/Cargo.toml b/crates/pack-napi/Cargo.toml index 4e6a83a43..75106631d 100644 --- a/crates/pack-napi/Cargo.toml +++ b/crates/pack-napi/Cargo.toml @@ -87,7 +87,6 @@ pack-core = { workspace = true } turbo-tasks-malloc = { workspace = true, default-features = false, features = [ "custom_allocator", ] } - turbopack = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript-hmr-protocol = { workspace = true } diff --git a/crates/pack-napi/src/pack_api/project.rs b/crates/pack-napi/src/pack_api/project.rs index 39b505c59..a1cb67f40 100644 --- a/crates/pack-napi/src/pack_api/project.rs +++ b/crates/pack-napi/src/pack_api/project.rs @@ -23,10 +23,7 @@ use pack_api::{ hmr_update_with_issues_operation, }, operation::EntrypointsOperation, - project::{ - DefineEnv, PartialProjectOptions, ProjectContainer, ProjectInstance, ProjectOptions, - WatchOptions, - }, + project::{DefineEnv, PartialProjectOptions, ProjectContainer, ProjectOptions, WatchOptions}, source_map::get_source_map_rope, tasks::{BundlerTurboTasks, RootTask}, }; @@ -54,7 +51,9 @@ use turbopack_core::{ }; use turbopack_ecmascript_hmr_protocol::{ClientUpdateInstruction, ResourceIdentifier}; use turbopack_trace_utils::{ - exit::ExitHandler, filter_layer::FilterLayer, raw_trace::RawTraceLayer, + exit::{ExitHandler, ExitReceiver}, + filter_layer::FilterLayer, + raw_trace::RawTraceLayer, trace_writer::TraceWriter, }; @@ -240,6 +239,12 @@ impl From for DefineEnv { } } +pub struct ProjectInstance { + pub turbo_tasks: BundlerTurboTasks, + pub container: ResolvedVc, + pub exit_receiver: tokio::sync::Mutex>, +} + #[napi(ts_return_type = "Promise<{ __napiType: \"Project\" }>")] pub async fn project_new( options: NapiProjectOptions, diff --git a/crates/pack-napi/src/pack_api/utils.rs b/crates/pack-napi/src/pack_api/utils.rs index 3cece42f3..31652c2e1 100644 --- a/crates/pack-napi/src/pack_api/utils.rs +++ b/crates/pack-napi/src/pack_api/utils.rs @@ -6,7 +6,10 @@ use napi::{ bindgen_prelude::{External, ToNapiValue}, threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; -use pack_api::tasks::{BundlerTurboTasks, RootTask}; +use pack_api::{ + tasks::{BundlerTurboTasks, RootTask}, + utils::StyledStringSerialize, +}; use rustc_hash::FxHashMap; use serde::Serialize; use turbo_tasks::{ @@ -186,42 +189,6 @@ impl From<&PlainIssue> for NapiIssue { } } -#[derive(Serialize)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum StyledStringSerialize<'a> { - Line { - value: Vec>, - }, - Stack { - value: Vec>, - }, - Text { - value: &'a str, - }, - Code { - value: &'a str, - }, - Strong { - value: &'a str, - }, -} - -impl<'a> From<&'a StyledString> for StyledStringSerialize<'a> { - fn from(value: &'a StyledString) -> Self { - match value { - StyledString::Line(parts) => StyledStringSerialize::Line { - value: parts.iter().map(|p| p.into()).collect(), - }, - StyledString::Stack(parts) => StyledStringSerialize::Stack { - value: parts.iter().map(|p| p.into()).collect(), - }, - StyledString::Text(string) => StyledStringSerialize::Text { value: string }, - StyledString::Code(string) => StyledStringSerialize::Code { value: string }, - StyledString::Strong(string) => StyledStringSerialize::Strong { value: string }, - } - } -} - #[napi(object)] pub struct NapiIssueSource { pub source: NapiSource, diff --git a/crates/pack-tests/tests/snapshot/basic/alias/output/crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js b/crates/pack-tests/tests/snapshot/basic/alias/output/crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js index 1c955fe1f..f3926ec34 100644 --- a/crates/pack-tests/tests/snapshot/basic/alias/output/crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js +++ b/crates/pack-tests/tests/snapshot/basic/alias/output/crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js @@ -18,4 +18,4 @@ console.log(__TURBOPACK__imported__module__50__["foo"]); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_alias_input_f0054a20.js.map diff --git a/crates/pack-tests/tests/snapshot/basic/copy/output/crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js b/crates/pack-tests/tests/snapshot/basic/copy/output/crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js index 9512bb354..e1deda1a4 100644 --- a/crates/pack-tests/tests/snapshot/basic/copy/output/crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js +++ b/crates/pack-tests/tests/snapshot/basic/copy/output/crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js @@ -5,4 +5,4 @@ console.log('copy example'); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_copy_input_index_df61b525.js.map diff --git a/crates/pack-tests/tests/snapshot/basic/jsx/output/crates_pack-tests_tests_snapshot_4b0f162b.js b/crates/pack-tests/tests/snapshot/basic/jsx/output/crates_pack-tests_tests_snapshot_4b0f162b.js index cba49353c..eb8c6db1c 100644 --- a/crates/pack-tests/tests/snapshot/basic/jsx/output/crates_pack-tests_tests_snapshot_4b0f162b.js +++ b/crates/pack-tests/tests/snapshot/basic/jsx/output/crates_pack-tests_tests_snapshot_4b0f162b.js @@ -34,4 +34,4 @@ const __TURBOPACK__default__export__ = App; }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_4b0f162b.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_4b0f162b.js.map diff --git a/crates/pack-tests/tests/snapshot/basic/minify/output/crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js b/crates/pack-tests/tests/snapshot/basic/minify/output/crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js index 014fe4471..ebe87b0e8 100644 --- a/crates/pack-tests/tests/snapshot/basic/minify/output/crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js +++ b/crates/pack-tests/tests/snapshot/basic/minify/output/crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js @@ -11,4 +11,4 @@ function getMessage() { }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_basic_minify_input_index_39e670dc.js.map diff --git a/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js b/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js index 05a17185c..6104ca4f6 100644 --- a/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js +++ b/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js @@ -30,4 +30,4 @@ console.log('c', __TURBOPACK__imported__module__6__2["default"]); }), ]); -//# sourceMappingURL=38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map \ No newline at end of file +//# sourceMappingURL=38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map diff --git a/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map b/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map index c5be1577c..b00bfd329 100644 --- a/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map +++ b/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/output/38c48_pack-tests_tests_snapshot_concatenate_modules_duplicate-imports_input_6b37fe6e.js.map @@ -4,4 +4,4 @@ "sections": [ {"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/input/shared.js"],"sourcesContent":["module.exports = 'shared'\n"],"names":[],"mappings":"AAAA,OAAO,OAAO,GAAG"}}, {"offset": {"line": 8, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/input/a.js","turbopack:///[project]/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/input/b.js","turbopack:///[project]/crates/pack-tests/tests/snapshot/concatenate_modules/duplicate-imports/input/c.js"],"sourcesContent":["import shared from './shared.js'\n\nconsole.log('a', shared)\n","import './a.js'\nimport shared from './shared.js'\n\nconsole.log('a', shared)\n","import './b.js'\nimport shared from './shared.js'\n\nconsole.log('c', shared)\n"],"names":[],"mappings":";;;;;;;;;AAAA;;AAEA,QAAQ,GAAG,CAAC,KAAK,6CAAM;;;;ACCvB,QAAQ,GAAG,CAAC,KAAK,8CAAM;;;;ACAvB,QAAQ,GAAG,CAAC,KAAK,8CAAM"}}] -} \ No newline at end of file +} diff --git a/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js b/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js index 36ffa4e7d..7bb0648fb 100644 --- a/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js +++ b/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js @@ -28,4 +28,4 @@ console.log("a2"); }), ]); -//# sourceMappingURL=75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js.map \ No newline at end of file +//# sourceMappingURL=75859_tests_snapshot_concatenate_modules_side-effects-import_input_7857eab4.js.map diff --git a/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js b/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js index e8fbc4eb3..d37d38cde 100644 --- a/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js +++ b/crates/pack-tests/tests/snapshot/concatenate_modules/side-effects-import/output/75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js @@ -28,4 +28,4 @@ console.log("b2"); }), ]); -//# sourceMappingURL=75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js.map \ No newline at end of file +//# sourceMappingURL=75859_tests_snapshot_concatenate_modules_side-effects-import_input_b01ff421.js.map diff --git a/crates/pack-tests/tests/snapshot/define/basic/output/crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js b/crates/pack-tests/tests/snapshot/define/basic/output/crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js index 865d7a8fd..980a615ee 100644 --- a/crates/pack-tests/tests/snapshot/define/basic/output/crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js +++ b/crates/pack-tests/tests/snapshot/define/basic/output/crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js @@ -7,4 +7,4 @@ console.log(("TURBOPACK compile-time value", 1)); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_define_basic_input_index_ts_4dd2c2be.js.map diff --git a/crates/pack-tests/tests/snapshot/externals/subpath/output/__16b79216.js b/crates/pack-tests/tests/snapshot/externals/subpath/output/__16b79216.js index bb5602689..de145e277 100644 --- a/crates/pack-tests/tests/snapshot/externals/subpath/output/__16b79216.js +++ b/crates/pack-tests/tests/snapshot/externals/subpath/output/__16b79216.js @@ -57,4 +57,4 @@ console.log(__TURBOPACK__imported__module__99__["default"], void 0, void 0); }), ]); -//# sourceMappingURL=__16b79216.js.map \ No newline at end of file +//# sourceMappingURL=__16b79216.js.map diff --git a/crates/pack-tests/tests/snapshot/runtime/app_build_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js b/crates/pack-tests/tests/snapshot/runtime/app_build_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js index fa9d66afa..3a185c772 100644 --- a/crates/pack-tests/tests/snapshot/runtime/app_build_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js +++ b/crates/pack-tests/tests/snapshot/runtime/app_build_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js @@ -5,4 +5,4 @@ console.log('Hello, world!'); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_runtime_app_build_runtime_input_index_4caede21.js.map diff --git a/crates/pack-tests/tests/snapshot/runtime/app_dev_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js b/crates/pack-tests/tests/snapshot/runtime/app_dev_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js index 1712bdc0a..efe9f6373 100644 --- a/crates/pack-tests/tests/snapshot/runtime/app_dev_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js +++ b/crates/pack-tests/tests/snapshot/runtime/app_dev_runtime/output/crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js @@ -5,4 +5,4 @@ console.log('Hello, world!'); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_runtime_app_dev_runtime_input_index_844ce608.js.map diff --git a/crates/pack-tests/tests/snapshot/runtime/library_build_runtime/output/main.js b/crates/pack-tests/tests/snapshot/runtime/library_build_runtime/output/main.js index 77d8830ab..1404d1d9e 100644 --- a/crates/pack-tests/tests/snapshot/runtime/library_build_runtime/output/main.js +++ b/crates/pack-tests/tests/snapshot/runtime/library_build_runtime/output/main.js @@ -423,8 +423,8 @@ function normalizeChunkPath(path) { } else if (path.startsWith("./")) { path = path.substring(2); } - if (path.endsWith("/")) { - path = path.slice(0, -1); + if (!path.endsWith("/")) { + path += "/"; } return path; } diff --git a/crates/pack-tests/tests/snapshot/webpack-loaders/custom-loader/output/crates_pack-tests_tests_snapshot_fe2230b9.js b/crates/pack-tests/tests/snapshot/webpack-loaders/custom-loader/output/crates_pack-tests_tests_snapshot_fe2230b9.js index c4fdf3474..d45d51e2a 100644 --- a/crates/pack-tests/tests/snapshot/webpack-loaders/custom-loader/output/crates_pack-tests_tests_snapshot_fe2230b9.js +++ b/crates/pack-tests/tests/snapshot/webpack-loaders/custom-loader/output/crates_pack-tests_tests_snapshot_fe2230b9.js @@ -38,4 +38,4 @@ function App() { }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_fe2230b9.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_fe2230b9.js.map diff --git a/crates/pack-tests/tests/snapshot/webpack-loaders/sass/output/crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js b/crates/pack-tests/tests/snapshot/webpack-loaders/sass/output/crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js index cf1af6da4..641390541 100644 --- a/crates/pack-tests/tests/snapshot/webpack-loaders/sass/output/crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js +++ b/crates/pack-tests/tests/snapshot/webpack-loaders/sass/output/crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js @@ -7,4 +7,4 @@ __turbopack_context__.s([]); }), ]); -//# sourceMappingURL=crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js.map \ No newline at end of file +//# sourceMappingURL=crates_pack-tests_tests_snapshot_webpack-loaders_sass_input_index_ts_7e205e1f.js.map diff --git a/crates/utoo-wasm/Cargo.toml b/crates/utoo-wasm/Cargo.toml new file mode 100644 index 000000000..bd4598d51 --- /dev/null +++ b/crates/utoo-wasm/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "utoo-wasm" +version = "0.1.0" +edition = "2021" + +description = "WASM bindings for utoo-pm & utoo-pack" + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["utoo-pack"] +utoo-pack = [ + "turbo-rcstr", + "turbo-tasks", + "turbo-tasks-fs", + "turbo-tasks-backend", + "turbopack-core", + "pack-core", + "pack-api", +] + +[dependencies] +wasm-bindgen = "0.2.100" +wasm-bindgen-futures = "0.4.50" +reqwest = { version = "0.12.22" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { workspace = true } +serde-wasm-bindgen = "0.6.5" +js-sys = "0.3" +petgraph = "0.6" +flate2 = "1.1.2" +tar = "0.4.44" +futures = { workspace = true } +anyhow = { workspace = true, features = ["backtrace"] } +tracing = { workspace = true } +tokio = { workspace = true, features = ["rt", "time", "rt-multi-thread"] } +tracing-subscriber = { workspace = true } +tracing-web = "0.1.3" +rustc-hash = { workspace = true } +tokio-fs-ext = { version = "0.6.1", features = ["opfs_offload", "opfs_watch"] } +console_error_panic_hook = { version = "0.1.7" } +opfs-project = "0.1.5" + +## turbpack specific - conditional on utoo-pack feature +turbo-rcstr = { workspace = true, optional = true } +turbo-tasks = { workspace = true, optional = true } +turbo-tasks-fs = { workspace = true, optional = true } +turbo-tasks-backend = { workspace = true, optional = true } +turbopack-core = { workspace = true, optional = true } +pack-core = { workspace = true, optional = true } +pack-api = { workspace = true, optional = true } + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] +parking_lot = { workspace = true, features = ["nightly"] } + + +[build-dependencies] +anyhow = { workspace = true } +turbo-tasks-build = { workspace = true } diff --git a/crates/utoo-wasm/build.rs b/crates/utoo-wasm/build.rs new file mode 100644 index 000000000..c6012c62e --- /dev/null +++ b/crates/utoo-wasm/build.rs @@ -0,0 +1,7 @@ +fn main() { + // Check if the utoo-pack feature is enabled via environment variable + if std::env::var("CARGO_FEATURE_UTOO_PACK").is_ok() { + use turbo_tasks_build::generate_register; + generate_register(); + } +} diff --git a/crates/utoo-wasm/src/lib.rs b/crates/utoo-wasm/src/lib.rs new file mode 100644 index 000000000..03ecd18d7 --- /dev/null +++ b/crates/utoo-wasm/src/lib.rs @@ -0,0 +1,58 @@ +#![cfg(all(target_family = "wasm", target_os = "unknown"))] + +extern crate console_error_panic_hook; + +use std::panic; + +use tracing_subscriber::{ + fmt::{ + self, + format::{FmtSpan, Pretty}, + }, + layer::SubscriberExt, + registry, + util::SubscriberInitExt, + EnvFilter, Layer, +}; + +use tracing_web::{performance_layer, MakeWebConsoleWriter}; +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(feature = "utoo-pack")] +pub(crate) mod pack; + +#[cfg(feature = "utoo-pack")] +mod opfs_offload; +mod project; +pub(crate) mod tokio_runtime; +pub use project::Project; + +#[wasm_bindgen(start)] +fn init_pack() { + panic::set_hook(Box::new(console_error_panic_hook::hook)); + + let fmt_layer = fmt::layer() + .without_time() + .with_span_events(FmtSpan::CLOSE) + .with_writer(MakeWebConsoleWriter::new()) + .with_filter(EnvFilter::new({ + let trace = vec!["pack_core=info", "pack_api=info", "utoo_wasm=info"]; + [ + trace, + // pack_core::tracing_presets::TRACING_OVERVIEW_TARGETS.to_vec(), + ] + .concat() + .join(",") + })); + + registry().with(fmt_layer).init(); + + #[cfg(feature = "utoo-pack")] + { + pack::register(); + + wasm_bindgen_futures::spawn_local(turbo_tasks_fs::wasm_fs_offload::server( + crate::opfs_offload::OpfsOffload, + )) + } +} diff --git a/crates/utoo-wasm/src/opfs_offload.rs b/crates/utoo-wasm/src/opfs_offload.rs new file mode 100644 index 000000000..f33a33f6b --- /dev/null +++ b/crates/utoo-wasm/src/opfs_offload.rs @@ -0,0 +1,55 @@ +use std::{io, path::Path}; +use tokio_fs_ext::{offload, watch, Metadata, ReadDir}; + +pub struct OpfsOffload; + +impl offload::FsOffload for OpfsOffload { + async fn read(&self, path: impl AsRef) -> io::Result> { + opfs_project::read(path).await + } + + async fn write(&self, path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()> { + opfs_project::write(path, content).await + } + + async fn copy(&self, from: impl AsRef, to: impl AsRef) -> io::Result { + opfs_project::copy(from, to).await + } + + async fn read_dir(&self, path: impl AsRef) -> io::Result { + opfs_project::read_dir(path).await.map(ReadDir::from_iter) + } + + async fn create_dir(&self, path: impl AsRef) -> io::Result<()> { + opfs_project::create_dir(path).await + } + + async fn create_dir_all(&self, path: impl AsRef) -> io::Result<()> { + opfs_project::create_dir_all(path).await + } + + async fn remove_file(&self, path: impl AsRef) -> io::Result<()> { + opfs_project::remove_file(path).await + } + + async fn remove_dir(&self, path: impl AsRef) -> io::Result<()> { + opfs_project::remove_dir(path).await + } + + async fn remove_dir_all(&self, path: impl AsRef) -> io::Result<()> { + opfs_project::remove_dir_all(path).await + } + + async fn metadata(&self, path: impl AsRef) -> io::Result { + opfs_project::metadata(path).await + } + + async fn watch_dir( + &self, + path: impl AsRef, + recursive: bool, + cb: impl Fn(watch::event::Event) + Send + Sync + 'static, + ) -> io::Result<()> { + watch::watch_dir(path, recursive, cb).await + } +} diff --git a/crates/utoo-wasm/src/pack.rs b/crates/utoo-wasm/src/pack.rs new file mode 100644 index 000000000..823da7846 --- /dev/null +++ b/crates/utoo-wasm/src/pack.rs @@ -0,0 +1,254 @@ +use rustc_hash::FxHashMap; +use serde_json::{json, Value}; +use std::{ops::Deref, path::PathBuf, str::FromStr, sync::Arc}; +use tokio::time::Instant; +use turbo_tasks_fs::FileContent; +use turbopack_core::{ + diagnostics::PlainDiagnostic, + error::PrettyPrintError, + issue::{PlainIssue, PlainIssueSource, PlainSource}, + source_pos::SourcePos as SourcePosInner, +}; + +use anyhow::{Context, Ok, Result}; +use pack_api::{ + endpoint::Endpoint, + entrypoint::{get_all_written_entrypoints_with_issues_operation, EntrypointsWithIssues}, + project::{ProjectContainer, ProjectOptions, WatchOptions}, + tasks::BundlerTurboTasks, + utils::StyledStringSerialize, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use turbo_rcstr::RcStr; +use turbo_tasks::{OperationVc, ReadConsistency, ResolvedVc, TurboTasks}; +use turbo_tasks_backend::{ + noop_backing_storage, BackendOptions, NoopBackingStorage, TurboTasksBackend, +}; + +pub fn register() { + pack_api::register(); + include!(concat!(env!("OUT_DIR"), "/register.rs")); +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] +pub struct PartialProjectOptions { + pub project_path: String, + + pub config: Option, +} + +pub struct PackProject { + pub turbo_tasks: BundlerTurboTasks, + pub container: ResolvedVc, +} + +impl PackProject { + pub async fn initialize(options: ProjectOptions) -> Result { + let turbo_tasks = create_turbo_tasks()?; + let container = turbo_tasks + .run_once(async move { + let project_container = ProjectContainer::new("utoopack-web".into(), false); + let project_container = project_container.to_resolved().await?; + project_container.initialize(options).await?; + Ok(project_container) + }) + .await?; + + Ok(PackProject { + turbo_tasks, + container, + }) + } + + pub async fn build(&self) -> Result { + let start = Instant::now(); + let turbo_tasks = self.turbo_tasks.clone(); + let container = self.container; + let (entrypoints, issues, diags) = turbo_tasks + .run_once(async move { + let entrypoints_with_issues_op = + get_all_written_entrypoints_with_issues_operation(container); + + let EntrypointsWithIssues { + entrypoints, + issues, + diagnostics, + effects, + } = &*entrypoints_with_issues_op + .read_strongly_consistent() + .await?; + effects.apply().await?; + + Ok((entrypoints.clone(), issues.clone(), diagnostics.clone())) + }) + .await?; + + tracing::info!("all project entrypoints wrote to disk."); + + tracing::info!( + "pack tasks with {} apps {} libraries finished in {:?}", + entrypoints + .apps + .as_ref() + .map(|apps| apps.0.len()) + .unwrap_or_default(), + entrypoints + .libraries + .as_ref() + .map(|libraries| libraries.0.len()) + .unwrap_or_default(), + start.elapsed() + ); + + Ok(TurbopackResult { + issues: issues.iter().map(|i| Issue::from(&**i)).collect(), + diagnostics: diags.iter().map(|d| Diagnostic::from(d)).collect(), + }) + } +} + +pub fn create_turbo_tasks() -> Result { + Ok(BundlerTurboTasks::Memory(TurboTasks::new( + turbo_tasks_backend::TurboTasksBackend::new( + turbo_tasks_backend::BackendOptions { + storage_mode: None, + dependency_tracking: true, + ..Default::default() + }, + noop_backing_storage(), + ), + ))) +} + +#[derive(Serialize, Deserialize)] +pub struct Issue { + pub severity: String, + pub stage: String, + pub file_path: String, + pub title: serde_json::Value, + pub description: Option, + pub detail: Option, + pub source: Option, + pub documentation_link: String, + pub import_traces: serde_json::Value, +} + +impl From<&PlainIssue> for Issue { + fn from(issue: &PlainIssue) -> Self { + Self { + description: issue + .description + .as_ref() + .map(|styled| serde_json::to_value(StyledStringSerialize::from(styled)).unwrap()), + stage: issue.stage.to_string(), + file_path: issue.file_path.to_string(), + detail: issue + .detail + .as_ref() + .map(|styled| serde_json::to_value(StyledStringSerialize::from(styled)).unwrap()), + documentation_link: issue.documentation_link.to_string(), + severity: issue.severity.as_str().to_string(), + source: issue.source.as_ref().map(|source| source.into()), + title: serde_json::to_value(StyledStringSerialize::from(&issue.title)).unwrap(), + import_traces: serde_json::to_value(&issue.import_traces).unwrap(), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct IssueSource { + pub source: Source, + pub range: Option, +} + +impl From<&PlainIssueSource> for IssueSource { + fn from( + PlainIssueSource { + asset: source, + range, + }: &PlainIssueSource, + ) -> Self { + Self { + source: (&**source).into(), + range: range.as_ref().map(|range| range.into()), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Source { + pub ident: String, + pub content: Option, +} + +impl From<&PlainSource> for Source { + fn from(source: &PlainSource) -> Self { + Self { + ident: source.ident.to_string(), + content: match &*source.content { + FileContent::Content(content) => match content.content().to_str() { + std::result::Result::Ok(str) => Some(str.into_owned()), + Err(_) => None, + }, + FileContent::NotFound => None, + }, + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct IssueSourceRange { + pub start: SourcePos, + pub end: SourcePos, +} + +impl From<&(SourcePosInner, SourcePosInner)> for IssueSourceRange { + fn from((start, end): &(SourcePosInner, SourcePosInner)) -> Self { + Self { + start: (*start).into(), + end: (*end).into(), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct SourcePos { + pub line: u32, + pub column: u32, +} + +impl From for SourcePos { + fn from(pos: SourcePosInner) -> Self { + Self { + line: pos.line, + column: pos.column, + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Diagnostic { + pub category: String, + pub name: String, + pub payload: FxHashMap, +} + +impl Diagnostic { + pub fn from(diagnostic: &PlainDiagnostic) -> Self { + Self { + category: diagnostic.category.to_string(), + name: diagnostic.name.to_string(), + payload: diagnostic + .payload + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct TurbopackResult { + pub issues: Vec, + pub diagnostics: Vec, +} diff --git a/crates/utoo-wasm/src/project.rs b/crates/utoo-wasm/src/project.rs new file mode 100644 index 000000000..519928406 --- /dev/null +++ b/crates/utoo-wasm/src/project.rs @@ -0,0 +1,299 @@ +use std::path::PathBuf; +use std::str::FromStr; +#[cfg(feature = "utoo-pack")] +use std::sync::Arc; + +use anyhow::Context; +use pack_api::project::WatchOptions; +use serde_wasm_bindgen::to_value; +use tokio_fs_ext::{DirEntry as RawDirEntry, Metadata as RawMetadata}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; + +use crate::tokio_runtime::init_tokio_runtime; + +#[cfg(feature = "utoo-pack")] +use super::{ + pack::{PackProject, PartialProjectOptions, TurbopackResult}, + tokio_runtime::TOKIO_RUNTIME, +}; + +use parking_lot::RwLock; + +#[wasm_bindgen] +pub struct Project { + #[cfg(feature = "utoo-pack")] + pack_project: RwLock>>, +} + +#[wasm_bindgen] +impl Project { + #[wasm_bindgen(constructor)] + pub fn new(cwd: String, thread_url: String) -> Project { + opfs_project::set_cwd(&cwd); + init_tokio_runtime(thread_url); + Project { + #[cfg(feature = "utoo-pack")] + pack_project: RwLock::new(None), + } + } + + #[wasm_bindgen(getter)] + pub fn cwd(&self) -> String { + opfs_project::get_cwd().to_string_lossy().to_string() + } + + #[wasm_bindgen] + pub async fn install(&self, package_lock: String) -> Result<(), String> { + opfs_project::package_manager::install_deps(&package_lock) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[cfg(feature = "utoo-pack")] + #[wasm_bindgen] + pub async fn build(&self) -> Result { + self.init_pack_project().await.map_err(|e| e.to_string())?; + + let pack_project = match self.pack_project.read().as_ref() { + Some(pack_project) => pack_project.clone(), + None => return Err("invalid pack project".to_string()), + }; + + TOKIO_RUNTIME + .with(|rt| { + rt.get() + .expect("tokio runtime not found") + .spawn(async move { pack_project.build().await }) + }) + .await + .map_err(|e| e.to_string())? + .map_or_else( + |e| Err(e.to_string()), + |turbopack_result| { + serde_wasm_bindgen::to_value(&turbopack_result).map_err(|e| e.to_string()) + }, + ) + } + + async fn init_pack_project(&self) -> anyhow::Result<()> { + if self.pack_project.read().is_none() { + use pack_api::project::ProjectOptions; + use turbo_rcstr::RcStr; + + let config = self.read_to_string("utoopack.json").await.ok(); + + let partial_options = PartialProjectOptions { + project_path: ".".into(), + config, + }; + let project_path: RcStr = partial_options.project_path.into(); + + let mode = "production"; + + let config = partial_options.config.map_or( + anyhow::Result::::Ok(format!(r#"{{ "mode": {mode}}}"#).into()), + |config| { + use std::str::FromStr; + + let mut val = serde_json::value::Value::from_str(&config)?; + if let serde_json::value::Value::Object(map) = &mut val { + map.insert("mode".to_string(), mode.into()); + } + Ok(val.to_string().into()) + }, + )?; + let options = ProjectOptions { + root_path: project_path.clone(), + project_path: project_path.clone(), + config, + build_id: project_path.clone(), + watch: WatchOptions { + enable: true, + ..Default::default() + }, + ..Default::default() + }; + + let pack_context = TOKIO_RUNTIME + .with(|rt| { + rt.get() + .expect("tokio runtime not found") + .spawn(PackProject::initialize(options)) + }) + .await + .context("fail to initialize pack project")??; + + let mut pack_project_guard = self.pack_project.write(); + *pack_project_guard = Some(Arc::new(pack_context)); + } + + Ok(()) + } + + #[cfg(not(feature = "utoo-pack"))] + #[wasm_bindgen] + pub async fn build(&self) -> Result<(), JsValue> { + Err(JsValue::from_str( + "Build functionality requires the 'utoo-pack' feature to be enabled", + )) + } + + #[wasm_bindgen] + pub async fn read(&self, path: &str) -> Result, String> { + opfs_project::read(path).await.map_err(|e| e.to_string()) + } + + #[wasm_bindgen(js_name = readToString)] + pub async fn read_to_string(&self, path: &str) -> Result { + let buf = opfs_project::read(path).await.map_err(|e| e.to_string())?; + Ok(unsafe { String::from_utf8_unchecked(buf) }) + } + + #[wasm_bindgen] + pub async fn write(&self, path: &str, content: &[u8]) -> Result<(), String> { + opfs_project::write(path, content) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = "writeString")] + pub async fn write_string(&self, path: &str, content: &str) -> Result<(), String> { + opfs_project::write(path, content) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = readDir)] + pub async fn read_dir(&self, path: &str) -> Result, String> { + let read_dir = opfs_project::read_dir(path) + .await + .map_err(|e| e.to_string())?; + + let ret = read_dir + .into_iter() + .map(DirEntry::try_from) + .collect::, std::io::Error>>() + .map_err(|e| e.to_string())?; + + Ok(ret) + } + + #[wasm_bindgen(js_name = createDir)] + pub async fn create_dir(&self, path: &str) -> Result<(), String> { + opfs_project::create_dir(path) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = createDirAll)] + pub async fn create_dir_all(&self, path: &str) -> Result<(), String> { + opfs_project::create_dir_all(path) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = copyFile)] + pub async fn copy_file(&self, src: &str, dst: &str) -> Result<(), String> { + opfs_project::copy(src, dst) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = removeFile)] + pub async fn remove_file(&self, path: &str) -> Result<(), String> { + opfs_project::remove_file(path) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + #[wasm_bindgen(js_name = removeDir)] + pub async fn remove_dir(&self, path: &str, recursive: bool) -> Result<(), String> { + if recursive { + opfs_project::remove_dir_all(path) + .await + .map_err(|e| e.to_string())?; + } else { + opfs_project::remove_dir(path) + .await + .map_err(|e| e.to_string())?; + } + + Ok(()) + } + + #[wasm_bindgen(js_name = metadata)] + pub async fn metadata(&self, path: &str) -> Result { + opfs_project::metadata(path) + .await + .and_then(Metadata::try_from) + .map_err(|e| e.to_string()) + } +} + +#[wasm_bindgen(inspectable)] +#[derive(Debug, Clone)] +pub struct DirEntry { + #[wasm_bindgen(getter_with_clone)] + pub name: String, + #[wasm_bindgen] + pub r#type: DirEntryType, +} + +#[wasm_bindgen] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DirEntryType { + File = "file", + Directory = "directory", +} + +impl TryFrom for DirEntry { + type Error = std::io::Error; + + fn try_from(raw: RawDirEntry) -> Result { + Ok(DirEntry { + r#type: { + let file_type = raw.file_type()?; + if file_type.is_dir() { + DirEntryType::Directory + } else if file_type.is_file() { + DirEntryType::File + } else { + return Err(std::io::Error::from(std::io::ErrorKind::Unsupported)); + } + }, + name: raw.file_name().to_string_lossy().to_string(), + }) + } +} + +#[wasm_bindgen(inspectable)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Metadata { + r#type: DirEntryType, + file_size: u64, +} + +impl TryFrom for Metadata { + type Error = std::io::Error; + + fn try_from(raw: RawMetadata) -> Result { + Ok(Metadata { + r#type: if raw.is_file() { + DirEntryType::File + } else if raw.is_dir() { + DirEntryType::Directory + } else { + return Err(std::io::Error::from(std::io::ErrorKind::Unsupported)); + }, + file_size: raw.len(), + }) + } +} diff --git a/crates/utoo-wasm/src/tokio_runtime.rs b/crates/utoo-wasm/src/tokio_runtime.rs new file mode 100644 index 000000000..21aa94d48 --- /dev/null +++ b/crates/utoo-wasm/src/tokio_runtime.rs @@ -0,0 +1,32 @@ +use std::{ + cell::OnceCell, + sync::{ + atomic::{AtomicUsize, Ordering}, + LazyLock, + }, + time::Duration, +}; + +use tokio::runtime; + +thread_local! { + pub static TOKIO_RUNTIME: OnceCell = const { OnceCell::new() }; +} + +pub fn init_tokio_runtime(worker_url: String) { + TOKIO_RUNTIME.with(|ctx| { + ctx.get_or_init(|| { + runtime::Builder::new_multi_thread() + // .enable_time() + .disable_lifo_slot() + .thread_name_fn(|| { + static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); + let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); + format!("tokio-runtime-worker-{id}") + }) + .wasm_bindgen_shim_url(worker_url.clone()) + .build() + .unwrap() + }); + }) +} diff --git a/examples/define/src/index.ts b/examples/define/src/index.ts index abce1592d..7ff238b7f 100644 --- a/examples/define/src/index.ts +++ b/examples/define/src/index.ts @@ -1,4 +1,3 @@ console.log(AAA); console.log(BBB); console.log(CCC); - diff --git a/examples/react/src/foo.ts b/examples/react/src/foo.ts index f88ff1715..10ef14c8a 100644 --- a/examples/react/src/foo.ts +++ b/examples/react/src/foo.ts @@ -1 +1 @@ -export const foo = 12121; \ No newline at end of file +export const foo = 12121; diff --git a/examples/react/src/index.tsx b/examples/react/src/index.tsx index 84173882d..8bf53c644 100644 --- a/examples/react/src/index.tsx +++ b/examples/react/src/index.tsx @@ -8,4 +8,4 @@ let x: TypeA = { content: 'wd' }; x; -ReactDOM.createRoot(document.getElementById('root')!).render(); \ No newline at end of file +ReactDOM.createRoot(document.getElementById("root")!).render(); diff --git a/examples/resolve/src/a.ts b/examples/resolve/src/a.ts index e2061d065..bd2bc24dc 100644 --- a/examples/resolve/src/a.ts +++ b/examples/resolve/src/a.ts @@ -1 +1 @@ -export const a = "aaa"; \ No newline at end of file +export const a = "aaa"; diff --git a/examples/resolve/src/index.ts b/examples/resolve/src/index.ts index e024de9a9..2ca058ee0 100644 --- a/examples/resolve/src/index.ts +++ b/examples/resolve/src/index.ts @@ -1,3 +1,3 @@ import { a } from "hello-a"; -console.log(a); \ No newline at end of file +console.log(a); diff --git a/examples/utooweb-demo/.gitignore b/examples/utooweb-demo/.gitignore new file mode 100644 index 000000000..8bab65bc0 --- /dev/null +++ b/examples/utooweb-demo/.gitignore @@ -0,0 +1,4 @@ +.turbopack +node_modules +dist +trace.log diff --git a/examples/utooweb-demo/README.md b/examples/utooweb-demo/README.md new file mode 100644 index 000000000..c4f8a2c7d --- /dev/null +++ b/examples/utooweb-demo/README.md @@ -0,0 +1,10 @@ +### A simple example to use @utoo/web. Steps: + +```sh +cd ../../packages/utoo-web +npm install +npm run install-toolchain +npm run dev # or npm run build +cd ../../examples/utooweb-demo +npm run start +``` diff --git a/examples/utooweb-demo/index.html b/examples/utooweb-demo/index.html new file mode 100644 index 000000000..f5f0df76d --- /dev/null +++ b/examples/utooweb-demo/index.html @@ -0,0 +1,14 @@ + + + + + + + Minimal Utooweb Example + + + +
+ + + diff --git a/examples/utooweb-demo/package.json b/examples/utooweb-demo/package.json new file mode 100644 index 000000000..ac1868390 --- /dev/null +++ b/examples/utooweb-demo/package.json @@ -0,0 +1,27 @@ +{ + "name": "utooweb-demo", + "version": "0.0.1", + "description": "", + "scripts": { + "start": "rm -rf dist && webpack serve" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "@utoo/web": "*", + "@monaco-editor/react": "^4.4.6" + }, + "devDependencies": { + "typescript": "^5.8.3", + "html-webpack-plugin": "^5.6.0", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.2.2", + "ts-loader": "^9.5.2", + "css-loader": "^7.1.2", + "mini-css-extract-plugin": "^2.9.4" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/examples/utooweb-demo/src/Project.tsx b/examples/utooweb-demo/src/Project.tsx new file mode 100644 index 000000000..a864f1b87 --- /dev/null +++ b/examples/utooweb-demo/src/Project.tsx @@ -0,0 +1,140 @@ +import React, { useMemo } from "react"; +import { Editor } from "./components/Editor"; +import { FileTreeItem } from "./components/FileTree"; +import { Panel } from "./components/Panel"; +import { Preview } from "./components/Preview"; +import { useBuild } from "./hooks/useBuild"; +import { useFileContent } from "./hooks/useFileContent"; +import { useFileTree } from "./hooks/useFileTree"; +import { useUtooProject } from "./hooks/useUtooProject"; +import "./styles.css"; + +const Project = () => { + const { project, isLoading, error: projectError } = useUtooProject(); + const { fileTree, handleDirectoryExpand } = useFileTree(project); + const { + selectedFilePath, + selectedFileContent, + setSelectedFileContent, + previewUrl, + fetchFileContent, + error: fileContentError, + } = useFileContent(project); + + const previewRef = React.useRef<{ reload: () => void }>(null); + + const { + isBuilding, + handleBuild, + error: buildError, + } = useBuild(project, fileTree, handleDirectoryExpand, () => { + if (previewRef.current) { + previewRef.current.reload(); + } + }); + + const error = projectError || fileContentError || buildError; + + const memoizedFileTree = useMemo(() => fileTree, [fileTree]); + + const buildButton = ( + + ); + + return ( +
+ + {isLoading && ( +

+ Installing dependencies... +

+ )} + {error && ( +

{error}

+ )} + {!isLoading && !error && ( +
    + {memoizedFileTree.map((item, index) => ( + + ))} +
+ )} +
+ + + + + + + + +
+ ); +}; + +export default Project; diff --git a/examples/utooweb-demo/src/components/Editor.tsx b/examples/utooweb-demo/src/components/Editor.tsx new file mode 100644 index 000000000..fb7342787 --- /dev/null +++ b/examples/utooweb-demo/src/components/Editor.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import MonacoEditor from "@monaco-editor/react"; + +interface EditorProps { + filePath: string; + content: string; + onContentChange: (newContent: string) => void; +} + +export const Editor = ({ filePath, content, onContentChange }: EditorProps) => { + const modelUri = filePath ? `file:///${filePath.replace(/^\.\//, '')}` : undefined; + + if (!filePath) { + return null; + } + + return ( + + filePath.endsWith(ext), + ) + ? "typescript" + : filePath.endsWith(".json") + ? "json" + : filePath.endsWith(".css") + ? "css" + : filePath.endsWith(".html") + ? "html" + : "plaintext" + } + value={content} + onChange={(value) => onContentChange(value || "")} + options={{ + readOnly: false, + fontSize: 14, + minimap: { enabled: false }, + scrollBeyondLastLine: false, + }} + /> + ); +}; diff --git a/examples/utooweb-demo/src/components/FileTree.tsx b/examples/utooweb-demo/src/components/FileTree.tsx new file mode 100644 index 000000000..c0cbe637d --- /dev/null +++ b/examples/utooweb-demo/src/components/FileTree.tsx @@ -0,0 +1,117 @@ +import React, { useCallback, useState } from "react"; +import { FileTreeNode, FileTreeItemProps } from "../types"; + +export const FileTreeItem = React.memo( + ({ + item, + onFileClick, + onDirectoryExpand, + selectedFile, + }: FileTreeItemProps & { + onDelete?: (item: FileTreeNode) => Promise; + }) => { + const [isCollapsed, setIsCollapsed] = useState(true); + const [isNodeLoading, setIsNodeLoading] = useState(false); + + const toggleCollapse = useCallback(async () => { + if (item.type === "directory") { + if (isCollapsed) { + if (item.children && item.children.length === 0) { + setIsNodeLoading(true); + await onDirectoryExpand?.(item); + setIsNodeLoading(false); + } + } + setIsCollapsed(!isCollapsed); + } else { + onFileClick(item.fullName); + } + }, [item, isCollapsed, onFileClick, onDirectoryExpand]); + + const handleRefresh = useCallback( + async (e: React.MouseEvent) => { + e.stopPropagation(); + if (item.type === "directory") { + await onDirectoryExpand?.(item); + } + }, + [onDirectoryExpand, item], + ); + + const isSelected = selectedFile === item.fullName; + + return ( +
  • +
    + + {item.type === "directory" ? (isCollapsed ? "▶" : "▼") : ""} + + {item.type === "file" ? "📄" : "📁"} + {item.name} + {isNodeLoading && ( + + Loading... + + )} + {item.type === "directory" && ( + + )} +
    + {item.type === "directory" && !isCollapsed && ( +
      + {item.children && + item.children.map((child: FileTreeNode) => ( + + ))} +
    + )} +
  • + ); + }, +); diff --git a/examples/utooweb-demo/src/components/Panel.tsx b/examples/utooweb-demo/src/components/Panel.tsx new file mode 100644 index 000000000..143fd41d6 --- /dev/null +++ b/examples/utooweb-demo/src/components/Panel.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +interface PanelProps { + title: React.ReactNode; + children: React.ReactNode; + actions?: React.ReactNode; + style?: React.CSSProperties; + contentStyle?: React.CSSProperties; +} + +export const Panel: React.FC = ({ title, children, actions, style, contentStyle }) => { + return ( +
    +
    +

    + {title} +

    + {actions &&
    {actions}
    } +
    +
    + {children} +
    +
    + ); +}; diff --git a/examples/utooweb-demo/src/components/Preview.tsx b/examples/utooweb-demo/src/components/Preview.tsx new file mode 100644 index 000000000..42da7cead --- /dev/null +++ b/examples/utooweb-demo/src/components/Preview.tsx @@ -0,0 +1,38 @@ +import React, { forwardRef, useImperativeHandle, useRef } from "react"; + +interface PreviewProps { + url: string; +} + +export const Preview = forwardRef(({ url }: PreviewProps, ref) => { + const iframeRef = useRef(null); + + useImperativeHandle(ref, () => ({ + reload: () => { + if (iframeRef.current) { + iframeRef.current.contentWindow?.location.reload(); + } + }, + })); + + return url ? ( +