From 29441fdedbe649797bbfd7610a4058ce51134f85 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 14 Sep 2024 08:41:28 -0400 Subject: [PATCH] v2 Cleanup (#9936) --- Cargo.lock | 2219 +----- crates/json-comments-rs/src/lib.rs | 12 +- crates/macros/src/lib.rs | 29 +- crates/macros/src/napi.rs | 22 +- crates/node-bindings/Cargo.toml | 47 +- .../src/file_system/file_system_napi.rs | 80 - crates/node-bindings/src/file_system/mod.rs | 2 - crates/node-bindings/src/hash.rs | 3 +- crates/node-bindings/src/image.rs | 18 +- crates/node-bindings/src/lib.rs | 7 - crates/node-bindings/src/parcel/mod.rs | 4 - crates/node-bindings/src/parcel/monitoring.rs | 11 - .../src/parcel/package_manager_napi.rs | 29 - crates/node-bindings/src/parcel/parcel.rs | 176 - crates/node-bindings/src/parcel/worker.rs | 40 - crates/node-bindings/src/resolver.rs | 63 +- crates/node-bindings/src/resolver_old.rs | 522 -- crates/node-bindings/src/transformer.rs | 4 +- crates/parcel/Cargo.toml | 36 - crates/parcel/src/lib.rs | 13 - crates/parcel/src/parcel.rs | 159 - crates/parcel/src/plugins.rs | 70 - crates/parcel/src/plugins/config_plugins.rs | 325 - crates/parcel/src/project_root.rs | 326 - crates/parcel/src/request_tracker/mod.rs | 11 - crates/parcel/src/request_tracker/request.rs | 124 - .../src/request_tracker/request_graph.rs | 18 - .../src/request_tracker/request_tracker.rs | 315 - crates/parcel/src/request_tracker/test.rs | 233 - crates/parcel/src/requests.rs | 26 - .../src/requests/asset_graph_request.rs | 543 -- crates/parcel/src/requests/asset_request.rs | 138 - crates/parcel/src/requests/entry_request.rs | 144 - crates/parcel/src/requests/path_request.rs | 404 -- crates/parcel/src/requests/target_request.rs | 1259 ---- .../requests/target_request/package_json.rs | 306 - crates/parcel/src/test_utils.rs | 95 - crates/parcel_config/Cargo.toml | 24 - crates/parcel_config/src/lib.rs | 9 - crates/parcel_config/src/map.rs | 11 - crates/parcel_config/src/map/matcher.rs | 111 - .../src/map/named_pipelines_map.rs | 427 -- crates/parcel_config/src/map/pipeline_map.rs | 128 - crates/parcel_config/src/map/pipelines_map.rs | 132 - crates/parcel_config/src/parcel_config.rs | 145 - .../src/parcel_config_fixtures.rs | 246 - crates/parcel_config/src/parcel_rc.rs | 45 - .../src/parcel_rc_config_loader.rs | 852 --- .../src/partial_parcel_config.rs | 693 -- crates/parcel_core/Cargo.toml | 25 - crates/parcel_core/src/asset_graph.rs | 606 -- crates/parcel_core/src/bundle_graph.rs | 1 - crates/parcel_core/src/cache.rs | 8 - crates/parcel_core/src/config_loader.rs | 388 -- crates/parcel_core/src/hash.rs | 18 - crates/parcel_core/src/lib.rs | 7 - crates/parcel_core/src/plugin.rs | 56 - .../parcel_core/src/plugin/bundler_plugin.rs | 40 - .../src/plugin/compressor_plugin.rs | 46 - crates/parcel_core/src/plugin/namer_plugin.rs | 49 - .../src/plugin/optimizer_plugin.rs | 57 - .../parcel_core/src/plugin/packager_plugin.rs | 48 - .../parcel_core/src/plugin/reporter_plugin.rs | 65 - .../composite_reporter_plugin.rs | 98 - .../parcel_core/src/plugin/resolver_plugin.rs | 99 - .../parcel_core/src/plugin/runtime_plugin.rs | 58 - .../src/plugin/transformer_plugin.rs | 112 - .../src/plugin/validator_plugin.rs | 94 - crates/parcel_core/src/types.rs | 33 - crates/parcel_core/src/types/asset.rs | 200 - crates/parcel_core/src/types/bundle.rs | 92 - crates/parcel_core/src/types/bundle_graph.rs | 1 - crates/parcel_core/src/types/dependency.rs | 266 - crates/parcel_core/src/types/diagnostic.rs | 208 - crates/parcel_core/src/types/environment.rs | 217 - .../src/types/environment/browsers.rs | 188 - .../src/types/environment/engines.rs | 125 - .../src/types/environment/output_format.rs | 37 - .../src/types/environment/version.rs | 249 - crates/parcel_core/src/types/file.rs | 9 - crates/parcel_core/src/types/file_type.rs | 69 - crates/parcel_core/src/types/invalidation.rs | 2 - crates/parcel_core/src/types/json.rs | 1 - crates/parcel_core/src/types/package_json.rs | 76 - .../parcel_core/src/types/parcel_options.rs | 110 - crates/parcel_core/src/types/source.rs | 29 - crates/parcel_core/src/types/symbol.rs | 33 - crates/parcel_core/src/types/target.rs | 54 - crates/parcel_core/src/types/utils.rs | 23 - crates/parcel_filesystem/Cargo.toml | 17 - .../src/in_memory_file_system.rs | 252 - crates/parcel_filesystem/src/lib.rs | 67 - .../parcel_filesystem/src/os_file_system.rs | 39 - .../src/os_file_system/canonicalize.rs | 150 - crates/parcel_filesystem/src/search.rs | 393 -- crates/parcel_monitoring/Cargo.toml | 34 - crates/parcel_monitoring/README.md | 54 - .../examples/sample_usage.rs | 108 - .../parcel_monitoring/src/crash_reporter.rs | 191 - crates/parcel_monitoring/src/from_env.rs | 19 - crates/parcel_monitoring/src/lib.rs | 107 - .../src/sentry_integration.rs | 171 - crates/parcel_monitoring/src/tracer.rs | 140 - crates/parcel_napi_helpers/Cargo.toml | 22 - crates/parcel_napi_helpers/src/anyhow.rs | 10 - crates/parcel_napi_helpers/src/call_method.rs | 37 - crates/parcel_napi_helpers/src/console_log.rs | 24 - .../parcel_napi_helpers/src/get_function.rs | 31 - .../src/js_callable/js_callable.rs | 159 - .../src/js_callable/js_value.rs | 19 - .../src/js_callable/mod.rs | 7 - .../src/js_callable/serde.rs | 39 - crates/parcel_napi_helpers/src/lib.rs | 14 - .../parcel_napi_helpers/src/transferable.rs | 89 - crates/parcel_package_manager/Cargo.toml | 12 - crates/parcel_package_manager/src/lib.rs | 5 - .../src/node_package_manager.rs | 33 - .../src/package_manager.rs | 20 - crates/parcel_plugin_resolver/Cargo.toml | 13 - crates/parcel_plugin_resolver/src/lib.rs | 3 - .../src/parcel_resolver.rs | 550 -- crates/parcel_plugin_rpc/Cargo.toml | 19 - crates/parcel_plugin_rpc/src/lib.rs | 7 - crates/parcel_plugin_rpc/src/nodejs/mod.rs | 7 - .../src/nodejs/rpc_conn_nodejs.rs | 32 - .../src/nodejs/rpc_conns_nodejs.rs | 41 - .../src/nodejs/rpc_host_nodejs.rs | 42 - .../parcel_plugin_rpc/src/plugin/bundler.rs | 35 - .../src/plugin/compressor.rs | 32 - crates/parcel_plugin_rpc/src/plugin/mod.rs | 19 - crates/parcel_plugin_rpc/src/plugin/namer.rs | 36 - .../parcel_plugin_rpc/src/plugin/optimizer.rs | 32 - .../parcel_plugin_rpc/src/plugin/packager.rs | 32 - .../parcel_plugin_rpc/src/plugin/reporter.rs | 32 - .../parcel_plugin_rpc/src/plugin/resolver.rs | 29 - .../parcel_plugin_rpc/src/plugin/runtime.rs | 37 - .../src/plugin/transformer.rs | 33 - crates/parcel_plugin_rpc/src/rpc_host.rs | 12 - .../parcel_plugin_transformer_js/Cargo.toml | 15 - .../parcel_plugin_transformer_js/src/lib.rs | 6 - .../src/transformer.rs | 411 -- .../src/transformer/conversion.rs | 621 -- .../transformer/conversion/dependency_kind.rs | 170 - .../src/transformer/conversion/loc.rs | 20 - .../src/transformer/conversion/symbol.rs | 170 - .../src/transformer/test_helpers.rs | 20 - .../src/ts_config.rs | 75 - packages/core/core/src/Parcel.js | 35 +- packages/core/core/src/RequestTracker.js | 11 +- packages/core/core/src/index.js | 2 - packages/core/core/src/parcel-v3/ParcelV3.js | 73 - packages/core/core/src/parcel-v3/fs.js | 36 - packages/core/core/src/parcel-v3/index.js | 5 - .../core/core/src/parcel-v3/jsCallable.js | 13 - .../core/src/parcel-v3/plugins/Resolver.js | 9 - .../core/core/src/parcel-v3/plugins/index.js | 3 - .../core/core/src/parcel-v3/worker/index.js | 8 - .../core/core/src/parcel-v3/worker/worker.js | 14 - .../src/requests/AssetGraphRequestRust.js | 222 - .../core/src/requests/BundleGraphRequest.js | 7 +- packages/core/core/test/test-utils.js | 2 +- packages/core/feature-flags/src/index.js | 2 - packages/core/feature-flags/src/types.js | 8 - .../integration-tests/test/BundleGraph.js | 4 +- packages/core/integration-tests/test/api.js | 4 +- packages/core/integration-tests/test/babel.js | 4 +- .../core/integration-tests/test/blob-url.js | 4 +- .../core/integration-tests/test/bundler.js | 4 +- packages/core/integration-tests/test/cache.js | 7 +- .../integration-tests/test/compressors.js | 4 +- .../integration-tests/test/config-merging.js | 4 +- .../integration-tests/test/contentHashing.js | 11 +- .../integration-tests/test/css-modules.js | 4 +- packages/core/integration-tests/test/css.js | 4 +- packages/core/integration-tests/test/elm.js | 4 +- .../core/integration-tests/test/encodedURI.js | 4 +- .../test/eslint-validation.js | 4 +- .../integration-tests/test/feature-flags.js | 13 +- packages/core/integration-tests/test/fs.js | 4 +- packages/core/integration-tests/test/glob.js | 4 +- .../core/integration-tests/test/globals.js | 11 +- packages/core/integration-tests/test/glsl.js | 4 +- .../core/integration-tests/test/graphql.js | 4 +- packages/core/integration-tests/test/hmr.js | 4 +- packages/core/integration-tests/test/html.js | 4 +- packages/core/integration-tests/test/image.js | 11 +- .../test/incremental-bundling.js | 6 +- .../core/integration-tests/test/javascript.js | 6066 ++++++++--------- .../integration-tests/test/json-reporter.js | 4 +- .../core/integration-tests/test/kotlin.js | 2 +- .../integration-tests/test/lazy-compile.js | 4 +- packages/core/integration-tests/test/less.js | 4 +- .../integration-tests/test/library-bundler.js | 4 +- .../core/integration-tests/test/macros.js | 4 +- .../core/integration-tests/test/markdown.js | 8 +- packages/core/integration-tests/test/mdx.js | 4 +- .../test/metrics-reporter.js | 4 +- .../core/integration-tests/test/monorepos.js | 4 +- packages/core/integration-tests/test/namer.js | 4 +- .../integration-tests/test/output-formats.js | 4 +- .../core/integration-tests/test/packager.js | 4 +- .../integration-tests/test/parcel-link.js | 4 +- .../integration-tests/test/parcel-query.js | 4 +- .../core/integration-tests/test/parcel-v3.js | 36 - .../core/integration-tests/test/parser.js | 8 +- .../core/integration-tests/test/plugin.js | 4 +- packages/core/integration-tests/test/pnp.js | 11 +- .../core/integration-tests/test/postcss.js | 4 +- .../core/integration-tests/test/posthtml.js | 4 +- packages/core/integration-tests/test/proxy.js | 4 +- packages/core/integration-tests/test/pug.js | 13 +- .../integration-tests/test/react-refresh.js | 4 +- .../test/registered-plugins.js | 11 +- .../core/integration-tests/test/reporters.js | 4 +- .../core/integration-tests/test/resolver.js | 14 +- packages/core/integration-tests/test/sass.js | 4 +- .../integration-tests/test/schema-jsonld.js | 11 +- .../integration-tests/test/scope-hoisting.js | 4 +- .../core/integration-tests/test/server.js | 4 +- .../core/integration-tests/test/sourcemaps.js | 4 +- .../core/integration-tests/test/stylus.js | 4 +- .../core/integration-tests/test/sugarss.js | 11 +- .../core/integration-tests/test/svg-react.js | 4 +- packages/core/integration-tests/test/svg.js | 11 +- .../test/symbol-propagation.js | 11 +- .../integration-tests/test/tailwind-tests.js | 4 +- .../core/integration-tests/test/tracing.js | 4 +- .../integration-tests/test/transpilation.js | 4 +- .../core/integration-tests/test/ts-types.js | 4 +- .../integration-tests/test/ts-validation.js | 4 +- .../integration-tests/test/typescript-tsc.js | 4 +- .../core/integration-tests/test/typescript.js | 4 +- packages/core/integration-tests/test/vue.js | 4 +- packages/core/integration-tests/test/wasm.js | 9 +- .../core/integration-tests/test/watcher.js | 4 +- .../integration-tests/test/webextension.js | 11 +- .../integration-tests/test/webmanifest.js | 11 +- .../core/integration-tests/test/workers.js | 4 +- .../core/integration-tests/test/worklets.js | 4 +- packages/core/integration-tests/test/xml.js | 10 +- packages/core/rust/index.js.flow | 94 +- packages/core/rust/package.json | 2 +- packages/core/test-utils/src/utils.js | 94 - packages/transformers/js/core/src/collect.rs | 48 +- .../js/core/src/constant_module.rs | 32 +- .../js/core/src/dependency_collector.rs | 54 +- .../transformers/js/core/src/env_replacer.rs | 21 +- packages/transformers/js/core/src/fs.rs | 36 +- .../js/core/src/global_replacer.rs | 42 +- packages/transformers/js/core/src/hoist.rs | 81 +- packages/transformers/js/core/src/lib.rs | 106 +- packages/transformers/js/core/src/modules.rs | 36 +- .../transformers/js/core/src/node_replacer.rs | 37 +- .../transformers/js/core/src/test_utils.rs | 28 +- .../js/core/src/typeof_replacer.rs | 15 +- packages/transformers/js/core/src/utils.rs | 31 +- .../utils/dev-dep-resolver-old/Cargo.toml | 13 - .../utils/dev-dep-resolver-old/src/lib.rs | 600 -- .../utils/dev-dep-resolver-old/src/main.rs | 50 - packages/utils/dev-dep-resolver/src/lib.rs | 43 +- packages/utils/dev-dep-resolver/src/main.rs | 10 +- .../utils/node-resolver-core/package.json | 1 - .../utils/node-resolver-core/src/Wrapper.js | 11 +- .../utils/node-resolver-core/src/index.js | 13 +- .../utils/node-resolver-core/test/resolver.js | 4822 +++++++------ .../utils/node-resolver-rs-old/Cargo.toml | 33 - .../node-resolver-rs-old/src/builtins.rs | 68 - .../utils/node-resolver-rs-old/src/cache.rs | 228 - .../utils/node-resolver-rs-old/src/error.rs | 117 - .../node-resolver-rs-old/src/invalidations.rs | 89 - .../utils/node-resolver-rs-old/src/lib.rs | 2816 -------- .../node-resolver-rs-old/src/package_json.rs | 1611 ----- .../utils/node-resolver-rs-old/src/path.rs | 58 - .../node-resolver-rs-old/src/specifier.rs | 324 - .../node-resolver-rs-old/src/tsconfig.rs | 309 - .../node-resolver-rs-old/src/url_to_path.rs | 146 - packages/utils/node-resolver-rs/Cargo.toml | 15 +- .../benches/node_resolver_bench.rs | 324 - packages/utils/node-resolver-rs/src/cache.rs | 187 +- packages/utils/node-resolver-rs/src/error.rs | 48 +- packages/utils/node-resolver-rs/src/fs.rs | 41 + .../node-resolver-rs/src/invalidations.rs | 38 +- packages/utils/node-resolver-rs/src/lib.rs | 190 +- .../node-resolver-rs/src/package_json.rs | 415 +- packages/utils/node-resolver-rs/src/path.rs | 157 +- .../utils/node-resolver-rs/src/specifier.rs | 94 +- .../utils/node-resolver-rs/src/tsconfig.rs | 130 +- .../utils/node-resolver-rs/src/url_to_path.rs | 2 +- yarn.lock | 36 +- 289 files changed, 6448 insertions(+), 33694 deletions(-) delete mode 100644 crates/node-bindings/src/file_system/file_system_napi.rs delete mode 100644 crates/node-bindings/src/file_system/mod.rs delete mode 100644 crates/node-bindings/src/parcel/mod.rs delete mode 100644 crates/node-bindings/src/parcel/monitoring.rs delete mode 100644 crates/node-bindings/src/parcel/package_manager_napi.rs delete mode 100644 crates/node-bindings/src/parcel/parcel.rs delete mode 100644 crates/node-bindings/src/parcel/worker.rs delete mode 100644 crates/node-bindings/src/resolver_old.rs delete mode 100644 crates/parcel/Cargo.toml delete mode 100644 crates/parcel/src/lib.rs delete mode 100644 crates/parcel/src/parcel.rs delete mode 100644 crates/parcel/src/plugins.rs delete mode 100644 crates/parcel/src/plugins/config_plugins.rs delete mode 100644 crates/parcel/src/project_root.rs delete mode 100644 crates/parcel/src/request_tracker/mod.rs delete mode 100644 crates/parcel/src/request_tracker/request.rs delete mode 100644 crates/parcel/src/request_tracker/request_graph.rs delete mode 100644 crates/parcel/src/request_tracker/request_tracker.rs delete mode 100644 crates/parcel/src/request_tracker/test.rs delete mode 100644 crates/parcel/src/requests.rs delete mode 100644 crates/parcel/src/requests/asset_graph_request.rs delete mode 100644 crates/parcel/src/requests/asset_request.rs delete mode 100644 crates/parcel/src/requests/entry_request.rs delete mode 100644 crates/parcel/src/requests/path_request.rs delete mode 100644 crates/parcel/src/requests/target_request.rs delete mode 100644 crates/parcel/src/requests/target_request/package_json.rs delete mode 100644 crates/parcel/src/test_utils.rs delete mode 100644 crates/parcel_config/Cargo.toml delete mode 100644 crates/parcel_config/src/lib.rs delete mode 100644 crates/parcel_config/src/map.rs delete mode 100644 crates/parcel_config/src/map/matcher.rs delete mode 100644 crates/parcel_config/src/map/named_pipelines_map.rs delete mode 100644 crates/parcel_config/src/map/pipeline_map.rs delete mode 100644 crates/parcel_config/src/map/pipelines_map.rs delete mode 100644 crates/parcel_config/src/parcel_config.rs delete mode 100644 crates/parcel_config/src/parcel_config_fixtures.rs delete mode 100644 crates/parcel_config/src/parcel_rc.rs delete mode 100644 crates/parcel_config/src/parcel_rc_config_loader.rs delete mode 100644 crates/parcel_config/src/partial_parcel_config.rs delete mode 100644 crates/parcel_core/Cargo.toml delete mode 100644 crates/parcel_core/src/asset_graph.rs delete mode 100644 crates/parcel_core/src/bundle_graph.rs delete mode 100644 crates/parcel_core/src/cache.rs delete mode 100644 crates/parcel_core/src/config_loader.rs delete mode 100644 crates/parcel_core/src/hash.rs delete mode 100644 crates/parcel_core/src/lib.rs delete mode 100644 crates/parcel_core/src/plugin.rs delete mode 100644 crates/parcel_core/src/plugin/bundler_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/compressor_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/namer_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/optimizer_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/packager_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/reporter_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/reporter_plugin/composite_reporter_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/resolver_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/runtime_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/transformer_plugin.rs delete mode 100644 crates/parcel_core/src/plugin/validator_plugin.rs delete mode 100644 crates/parcel_core/src/types.rs delete mode 100644 crates/parcel_core/src/types/asset.rs delete mode 100644 crates/parcel_core/src/types/bundle.rs delete mode 100644 crates/parcel_core/src/types/bundle_graph.rs delete mode 100644 crates/parcel_core/src/types/dependency.rs delete mode 100644 crates/parcel_core/src/types/diagnostic.rs delete mode 100644 crates/parcel_core/src/types/environment.rs delete mode 100644 crates/parcel_core/src/types/environment/browsers.rs delete mode 100644 crates/parcel_core/src/types/environment/engines.rs delete mode 100644 crates/parcel_core/src/types/environment/output_format.rs delete mode 100644 crates/parcel_core/src/types/environment/version.rs delete mode 100644 crates/parcel_core/src/types/file.rs delete mode 100644 crates/parcel_core/src/types/file_type.rs delete mode 100644 crates/parcel_core/src/types/invalidation.rs delete mode 100644 crates/parcel_core/src/types/json.rs delete mode 100644 crates/parcel_core/src/types/package_json.rs delete mode 100644 crates/parcel_core/src/types/parcel_options.rs delete mode 100644 crates/parcel_core/src/types/source.rs delete mode 100644 crates/parcel_core/src/types/symbol.rs delete mode 100644 crates/parcel_core/src/types/target.rs delete mode 100644 crates/parcel_core/src/types/utils.rs delete mode 100644 crates/parcel_filesystem/Cargo.toml delete mode 100644 crates/parcel_filesystem/src/in_memory_file_system.rs delete mode 100644 crates/parcel_filesystem/src/lib.rs delete mode 100644 crates/parcel_filesystem/src/os_file_system.rs delete mode 100644 crates/parcel_filesystem/src/os_file_system/canonicalize.rs delete mode 100644 crates/parcel_filesystem/src/search.rs delete mode 100644 crates/parcel_monitoring/Cargo.toml delete mode 100644 crates/parcel_monitoring/README.md delete mode 100644 crates/parcel_monitoring/examples/sample_usage.rs delete mode 100644 crates/parcel_monitoring/src/crash_reporter.rs delete mode 100644 crates/parcel_monitoring/src/from_env.rs delete mode 100644 crates/parcel_monitoring/src/lib.rs delete mode 100644 crates/parcel_monitoring/src/sentry_integration.rs delete mode 100644 crates/parcel_monitoring/src/tracer.rs delete mode 100644 crates/parcel_napi_helpers/Cargo.toml delete mode 100644 crates/parcel_napi_helpers/src/anyhow.rs delete mode 100644 crates/parcel_napi_helpers/src/call_method.rs delete mode 100644 crates/parcel_napi_helpers/src/console_log.rs delete mode 100644 crates/parcel_napi_helpers/src/get_function.rs delete mode 100644 crates/parcel_napi_helpers/src/js_callable/js_callable.rs delete mode 100644 crates/parcel_napi_helpers/src/js_callable/js_value.rs delete mode 100644 crates/parcel_napi_helpers/src/js_callable/mod.rs delete mode 100644 crates/parcel_napi_helpers/src/js_callable/serde.rs delete mode 100644 crates/parcel_napi_helpers/src/lib.rs delete mode 100644 crates/parcel_napi_helpers/src/transferable.rs delete mode 100644 crates/parcel_package_manager/Cargo.toml delete mode 100644 crates/parcel_package_manager/src/lib.rs delete mode 100644 crates/parcel_package_manager/src/node_package_manager.rs delete mode 100644 crates/parcel_package_manager/src/package_manager.rs delete mode 100644 crates/parcel_plugin_resolver/Cargo.toml delete mode 100644 crates/parcel_plugin_resolver/src/lib.rs delete mode 100644 crates/parcel_plugin_resolver/src/parcel_resolver.rs delete mode 100644 crates/parcel_plugin_rpc/Cargo.toml delete mode 100644 crates/parcel_plugin_rpc/src/lib.rs delete mode 100644 crates/parcel_plugin_rpc/src/nodejs/mod.rs delete mode 100644 crates/parcel_plugin_rpc/src/nodejs/rpc_conn_nodejs.rs delete mode 100644 crates/parcel_plugin_rpc/src/nodejs/rpc_conns_nodejs.rs delete mode 100644 crates/parcel_plugin_rpc/src/nodejs/rpc_host_nodejs.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/bundler.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/compressor.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/mod.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/namer.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/optimizer.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/packager.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/reporter.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/resolver.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/runtime.rs delete mode 100644 crates/parcel_plugin_rpc/src/plugin/transformer.rs delete mode 100644 crates/parcel_plugin_rpc/src/rpc_host.rs delete mode 100644 crates/parcel_plugin_transformer_js/Cargo.toml delete mode 100644 crates/parcel_plugin_transformer_js/src/lib.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer/conversion.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer/conversion/dependency_kind.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer/conversion/loc.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer/conversion/symbol.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/transformer/test_helpers.rs delete mode 100644 crates/parcel_plugin_transformer_js/src/ts_config.rs delete mode 100644 packages/core/core/src/parcel-v3/ParcelV3.js delete mode 100644 packages/core/core/src/parcel-v3/fs.js delete mode 100644 packages/core/core/src/parcel-v3/index.js delete mode 100644 packages/core/core/src/parcel-v3/jsCallable.js delete mode 100644 packages/core/core/src/parcel-v3/plugins/Resolver.js delete mode 100644 packages/core/core/src/parcel-v3/plugins/index.js delete mode 100644 packages/core/core/src/parcel-v3/worker/index.js delete mode 100644 packages/core/core/src/parcel-v3/worker/worker.js delete mode 100644 packages/core/core/src/requests/AssetGraphRequestRust.js delete mode 100644 packages/core/integration-tests/test/parcel-v3.js delete mode 100644 packages/utils/dev-dep-resolver-old/Cargo.toml delete mode 100644 packages/utils/dev-dep-resolver-old/src/lib.rs delete mode 100644 packages/utils/dev-dep-resolver-old/src/main.rs delete mode 100644 packages/utils/node-resolver-rs-old/Cargo.toml delete mode 100644 packages/utils/node-resolver-rs-old/src/builtins.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/cache.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/error.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/invalidations.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/lib.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/package_json.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/path.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/specifier.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/tsconfig.rs delete mode 100644 packages/utils/node-resolver-rs-old/src/url_to_path.rs delete mode 100644 packages/utils/node-resolver-rs/benches/node_resolver_bench.rs create mode 100644 packages/utils/node-resolver-rs/src/fs.rs diff --git a/Cargo.lock b/Cargo.lock index c6e0ec75991..590fb66be61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,15 +12,6 @@ dependencies = [ "regex", ] -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - [[package]] name = "adler" version = "1.0.2" @@ -65,12 +56,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstyle" version = "1.0.7" @@ -82,9 +67,6 @@ name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" -dependencies = [ - "backtrace", -] [[package]] name = "arrayvec" @@ -136,33 +118,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "backtrace" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64-simd" version = "0.7.0" @@ -248,12 +209,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytecount" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" - [[package]] name = "bytemuck" version = "1.16.0" @@ -266,12 +221,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - [[package]] name = "camino" version = "1.1.7" @@ -304,12 +253,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.1.6" @@ -326,12 +269,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "chrono" version = "0.4.38" @@ -341,34 +278,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.5", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", + "windows-targets", ] [[package]] @@ -379,32 +289,13 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", - "clap_lex 0.2.4", + "clap_lex", "indexmap 1.9.3", - "strsim 0.10.0", + "strsim", "termcolor", "textwrap", ] -[[package]] -name = "clap" -version = "4.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" -dependencies = [ - "anstyle", - "clap_lex 0.7.2", -] - [[package]] name = "clap_lex" version = "0.2.4" @@ -414,27 +305,12 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "convert_case" version = "0.6.0" @@ -444,16 +320,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -469,30 +335,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crash-context" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3" -dependencies = [ - "cfg-if", - "libc", - "mach2", -] - -[[package]] -name = "crash-handler" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff62668cee357a18a67b5ce55bdef533d2c108d8eded362408f08face8731a71" -dependencies = [ - "cfg-if", - "crash-context", - "libc", - "mach2", - "parking_lot", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -502,42 +344,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap 4.5.11", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -572,12 +378,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -598,41 +398,6 @@ dependencies = [ "syn", ] -[[package]] -name = "darling" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -662,46 +427,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_builder" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "difflib" version = "0.4.0" @@ -724,24 +449,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" -[[package]] -name = "dyn-hash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a650a461c6a8ff1ef205ed9a2ad56579309853fecefc2423f73dced342f92258" - [[package]] name = "either" version = "1.12.0" @@ -770,7 +483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -806,19 +519,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall 0.4.1", - "windows-sys 0.52.0", -] - -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", + "windows-sys", ] [[package]] @@ -837,27 +538,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -867,12 +547,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" - [[package]] name = "from_variant" version = "0.1.8" @@ -896,56 +570,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -967,12 +591,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - [[package]] name = "glob" version = "0.3.1" @@ -1009,27 +627,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "goblin" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" -dependencies = [ - "log", - "plain", - "scroll", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1058,33 +655,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hostname" -version = "0.4.0" +name = "hstr" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" -dependencies = [ - "cfg-if", - "libc", - "windows", -] - -[[package]] -name = "hstr" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96274be293b8877e61974a607105d09c84caebe9620b47774aa8a6b942042dd4" +checksum = "96274be293b8877e61974a607105d09c84caebe9620b47774aa8a6b942042dd4" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -1094,124 +668,6 @@ dependencies = [ "triomphe", ] -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls 0.22.4", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower", - "tower-service", - "tracing", -] - [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1235,12 +691,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.5.0" @@ -1315,12 +765,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - [[package]] name = "is-macro" version = "0.3.5" @@ -1333,17 +777,6 @@ dependencies = [ "syn", ] -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is_elevated" version = "0.1.2" @@ -1400,9 +833,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1460,7 +893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] @@ -1495,62 +928,12 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miette" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" -dependencies = [ - "cfg-if", - "miette-derive", - "thiserror", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "mimalloc" version = "0.1.42" @@ -1560,69 +943,6 @@ dependencies = [ "libmimalloc-sys", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minidump-common" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4d14bcca0fd3ed165a03000480aaa364c6860c34e900cb2dafdf3b95340e77" -dependencies = [ - "bitflags 2.6.0", - "debugid", - "num-derive", - "num-traits", - "range-map", - "scroll", - "smart-default", -] - -[[package]] -name = "minidump-writer" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abcd9c8a1e6e1e9d56ce3627851f39a17ea83e17c96bc510f29d7e43d78a7d" -dependencies = [ - "bitflags 2.6.0", - "byteorder", - "cfg-if", - "crash-context", - "goblin", - "libc", - "log", - "mach2", - "memmap2", - "memoffset", - "minidump-common", - "nix", - "procfs-core", - "scroll", - "tempfile", - "thiserror", -] - -[[package]] -name = "minidumper" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4ebc9d1f8847ec1d078f78b35ed598e0ebefa1f242d5f83cd8d7f03960a7d1" -dependencies = [ - "cfg-if", - "crash-context", - "libc", - "log", - "minidump-writer", - "parking_lot", - "polling", - "scroll", - "thiserror", - "uds", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1639,49 +959,11 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "mockall" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "lazy_static", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "mozjpeg-sys" -version = "1.1.1" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c4fe4006093b2948ccb37bb413b6b9da2d654a9a50419b5861b0f4e8ad4da9" +checksum = "27e31c0171e0b1158c0dfb7386dbdf999f4a9afaa83fd68de39c7929f4d5c16f" dependencies = [ "cc", "dunce", @@ -1702,7 +984,6 @@ dependencies = [ "once_cell", "serde", "serde_json", - "tokio", ] [[package]] @@ -1751,28 +1032,11 @@ dependencies = [ [[package]] name = "nasm-rs" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" -dependencies = [ - "rayon", -] - -[[package]] -name = "native-tls" -version = "0.2.12" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "12fcfa1bd49e0342ec1d07ed2be83b59963e7acbeb9310e1bb2c07b69dadd959" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "jobserver", ] [[package]] @@ -1781,30 +1045,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nodejs-semver" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b8b91a6d799a23136abcf1a43ae9faed89298047272eee353eb701d195b18c" -dependencies = [ - "bytecount", - "miette", - "thiserror", - "winnow", -] - [[package]] name = "nom" version = "7.1.3" @@ -1815,16 +1055,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.5" @@ -1836,23 +1066,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -1881,15 +1094,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -1897,101 +1101,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "oorandom" -version = "11.1.4" +name = "os_str_bytes" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] -name = "openssl" -version = "0.10.64" +name = "outref" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "oxipng" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "os_info" -version = "3.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" -dependencies = [ - "log", - "serde", - "windows-sys 0.52.0", -] - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - -[[package]] -name = "outref" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "oxipng" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630638e107fb436644c300e781d3f17e1b04656138ba0d40564be4be3b06db32" +checksum = "630638e107fb436644c300e781d3f17e1b04656138ba0d40564be4be3b06db32" dependencies = [ "bitvec", - "clap 3.2.25", + "clap", "crossbeam-channel", "filetime", "image", @@ -2008,35 +1136,6 @@ dependencies = [ "zopfli", ] -[[package]] -name = "parcel" -version = "0.1.0" -dependencies = [ - "anyhow", - "dyn-hash", - "mockall", - "num_cpus", - "parcel-resolver", - "parcel_config", - "parcel_core", - "parcel_filesystem", - "parcel_package_manager", - "parcel_plugin_resolver", - "parcel_plugin_rpc", - "parcel_plugin_transformer_js", - "pathdiff", - "petgraph", - "rand", - "rayon", - "regex", - "serde", - "serde-bool", - "serde_json", - "tracing", - "tracing-subscriber", - "xxhash-rust", -] - [[package]] name = "parcel-dev-dep-resolver" version = "0.1.0" @@ -2049,18 +1148,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "parcel-dev-dep-resolver-old" -version = "0.1.0" -dependencies = [ - "dashmap", - "es-module-lexer", - "glob", - "parcel-resolver-old", - "rayon", - "serde_json", -] - [[package]] name = "parcel-js-swc-core" version = "0.1.0" @@ -2096,79 +1183,29 @@ dependencies = [ name = "parcel-node-bindings" version = "0.1.0" dependencies = [ - "anyhow", "crossbeam-channel", "dashmap", "getrandom", - "glob", "indexmap 1.9.3", "jemallocator", "libc", - "log", "mimalloc", - "mockall", "mozjpeg-sys", "napi", "napi-build", "napi-derive", - "num_cpus", - "once_cell", "oxipng", - "parcel", "parcel-dev-dep-resolver", - "parcel-dev-dep-resolver-old", "parcel-js-swc-core", "parcel-macros", "parcel-resolver", - "parcel-resolver-old", - "parcel_core", - "parcel_monitoring", - "parcel_napi_helpers", - "parcel_package_manager", - "parcel_plugin_transformer_js", - "parking_lot", "rayon", - "serde", - "serde_json", - "toml", - "tracing", - "tracing-appender", - "tracing-subscriber", "xxhash-rust", ] [[package]] name = "parcel-resolver" version = "0.1.0" -dependencies = [ - "assert_fs", - "bitflags 1.3.2", - "criterion", - "dashmap", - "glob-match", - "indexmap 1.9.3", - "is_elevated", - "itertools 0.10.5", - "json_comments", - "once_cell", - "parcel-resolver-old", - "parcel_core", - "parcel_filesystem", - "parking_lot", - "percent-encoding", - "serde", - "serde_json", - "serde_json5", - "thiserror", - "tracing", - "tracing-subscriber", - "url", - "xxhash-rust", -] - -[[package]] -name = "parcel-resolver-old" -version = "0.1.0" dependencies = [ "assert_fs", "bitflags 1.3.2", @@ -2180,145 +1217,15 @@ dependencies = [ "itertools 0.10.5", "json_comments", "once_cell", - "parcel_core", - "parcel_filesystem", "parking_lot", "percent-encoding", "serde", "serde_json", - "thiserror", - "tracing", "typed-arena", "url", "xxhash-rust", ] -[[package]] -name = "parcel_config" -version = "0.1.0" -dependencies = [ - "anyhow", - "derive_builder", - "glob-match", - "indexmap 2.2.6", - "mockall", - "parcel_core", - "parcel_filesystem", - "parcel_package_manager", - "pathdiff", - "serde", - "serde_json5", -] - -[[package]] -name = "parcel_core" -version = "0.1.0" -dependencies = [ - "anyhow", - "bitflags 2.6.0", - "browserslist-rs", - "derive_builder", - "dyn-hash", - "mockall", - "nodejs-semver", - "parcel_filesystem", - "petgraph", - "serde", - "serde-value", - "serde_json", - "serde_repr", - "xxhash-rust", -] - -[[package]] -name = "parcel_filesystem" -version = "0.1.0" -dependencies = [ - "anyhow", - "assert_fs", - "dashmap", - "is_elevated", - "mockall", - "xxhash-rust", -] - -[[package]] -name = "parcel_monitoring" -version = "0.1.0" -dependencies = [ - "anyhow", - "cfg-if", - "crash-handler", - "minidumper", - "sentry", - "serde", - "serde_json", - "thiserror", - "tracing", - "tracing-appender", - "tracing-subscriber", - "whoami", -] - -[[package]] -name = "parcel_napi_helpers" -version = "0.1.0" -dependencies = [ - "anyhow", - "napi", - "napi-build", - "once_cell", - "serde", -] - -[[package]] -name = "parcel_package_manager" -version = "0.1.0" -dependencies = [ - "anyhow", - "mockall", - "parcel-resolver", - "parcel_filesystem", - "serde", -] - -[[package]] -name = "parcel_plugin_resolver" -version = "0.1.0" -dependencies = [ - "anyhow", - "parcel-resolver", - "parcel_core", - "parcel_filesystem", -] - -[[package]] -name = "parcel_plugin_rpc" -version = "0.1.0" -dependencies = [ - "anyhow", - "napi", - "once_cell", - "parcel_config", - "parcel_core", - "parcel_napi_helpers", - "parking_lot", - "serde", -] - -[[package]] -name = "parcel_plugin_transformer_js" -version = "0.1.0" -dependencies = [ - "anyhow", - "indexmap 2.2.6", - "parcel-js-swc-core", - "parcel_core", - "parcel_filesystem", - "serde", - "swc_core", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -2339,7 +1246,7 @@ dependencies = [ "libc", "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] @@ -2360,51 +1267,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "petgraph" version = "0.6.5" @@ -2413,8 +1275,6 @@ checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap 2.2.6", - "serde", - "serde_derive", ] [[package]] @@ -2459,78 +1319,12 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "plotters" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" - -[[package]] -name = "plotters-svg" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" -dependencies = [ - "plotters-backend", -] - [[package]] name = "png" version = "0.17.13" @@ -2544,33 +1338,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "polling" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - [[package]] name = "predicates" version = "3.1.0" @@ -2625,16 +1392,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs-core" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" -dependencies = [ - "bitflags 2.6.0", - "hex", -] - [[package]] name = "psm" version = "0.1.21" @@ -2665,38 +1422,14 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "range-map" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" -dependencies = [ - "num-traits", -] +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" @@ -2765,51 +1498,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" -[[package]] -name = "reqwest" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.22.4", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.26.1", - "winreg", -] - [[package]] name = "rgb" version = "0.8.37" @@ -2819,27 +1507,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hash" version = "1.1.0" @@ -2874,70 +1541,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.4", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "windows-sys", ] [[package]] @@ -2967,15 +1571,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -2988,59 +1583,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scroll" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "0.9.0" @@ -3065,128 +1607,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "sentry" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00421ed8fa0c995f07cde48ba6c89e80f2b312f74ff637326f392fbfd23abe02" -dependencies = [ - "httpdate", - "native-tls", - "reqwest", - "rustls 0.21.12", - "sentry-anyhow", - "sentry-backtrace", - "sentry-contexts", - "sentry-core", - "sentry-debug-images", - "sentry-panic", - "sentry-tracing", - "tokio", - "ureq", - "webpki-roots 0.25.4", -] - -[[package]] -name = "sentry-anyhow" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcbce6e6785c2d91e67c55196f60ac561fab5946b6c7d60cc29f498fc126076" -dependencies = [ - "anyhow", - "sentry-backtrace", - "sentry-core", -] - -[[package]] -name = "sentry-backtrace" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79194074f34b0cbe5dd33896e5928bbc6ab63a889bd9df2264af5acb186921e" -dependencies = [ - "backtrace", - "once_cell", - "regex", - "sentry-core", -] - -[[package]] -name = "sentry-contexts" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba8870c5dba2bfd9db25c75574a11429f6b95957b0a78ac02e2970dd7a5249a" -dependencies = [ - "hostname", - "libc", - "os_info", - "rustc_version 0.4.0", - "sentry-core", - "uname", -] - -[[package]] -name = "sentry-core" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a75011ea1c0d5c46e9e57df03ce81f5c7f0a9e199086334a1f9c0a541e0826" -dependencies = [ - "once_cell", - "rand", - "sentry-types", - "serde", - "serde_json", -] - -[[package]] -name = "sentry-debug-images" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec2a486336559414ab66548da610da5e9626863c3c4ffca07d88f7dc71c8de8" -dependencies = [ - "findshlibs", - "once_cell", - "sentry-core", -] - -[[package]] -name = "sentry-panic" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eaa3ecfa3c8750c78dcfd4637cfa2598b95b52897ed184b4dc77fcf7d95060d" -dependencies = [ - "sentry-backtrace", - "sentry-core", -] - -[[package]] -name = "sentry-tracing" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f715932bf369a61b7256687c6f0554141b7ce097287e30e3f7ed6e9de82498fe" -dependencies = [ - "sentry-backtrace", - "sentry-core", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "sentry-types" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4519c900ce734f7a0eb7aba0869dfb225a7af8820634a7dd51449e3b093cfb7c" -dependencies = [ - "debugid", - "hex", - "rand", - "serde", - "serde_json", - "thiserror", - "time", - "url", - "uuid", -] - [[package]] name = "serde" version = "1.0.204" @@ -3196,25 +1616,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-bool" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af14f9242b0beec13757cf161feb2d600c11a764310425c3429dca9925b7a92" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_bytes" version = "0.11.14" @@ -3241,56 +1642,12 @@ version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ - "indexmap 2.2.6", "itoa", "memchr", "ryu", "serde", ] -[[package]] -name = "serde_json5" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a6b754515e1a7bd79fc2edeaecee526fc80cb3a918607e5ca149225a3a9586" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_spanned" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -3313,26 +1670,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "simd-abstraction" version = "0.7.1" @@ -3354,32 +1691,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "smartstring" version = "1.0.1" @@ -3391,16 +1708,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "sourcemap" version = "8.0.1" @@ -3420,12 +1727,6 @@ dependencies = [ "url", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "st-map" version = "0.2.3" @@ -3497,22 +1798,10 @@ dependencies = [ ] [[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.5.0" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "swc_atoms" @@ -4056,7 +2345,7 @@ version = "0.186.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446da32cac8299973aaf1d37496562bfd0c1e4f3c3ab5d0af6f07f42e8184102" dependencies = [ - "base64 0.21.7", + "base64", "dashmap", "indexmap 2.2.6", "once_cell", @@ -4204,12 +2493,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "tap" version = "1.0.1" @@ -4225,7 +2508,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -4279,47 +2562,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -4335,104 +2577,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.40" @@ -4444,18 +2588,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.27" @@ -4474,32 +2606,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -4512,12 +2618,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typed-arena" version = "2.0.2" @@ -4530,30 +2630,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "uds" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885c31f06fce836457fe3ef09a59f83fe8db95d270b11cd78f40a4666c4d1661" -dependencies = [ - "libc", -] - -[[package]] -name = "uname" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" -dependencies = [ - "libc", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4599,29 +2675,6 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "2.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" -dependencies = [ - "base64 0.22.1", - "log", - "native-tls", - "once_cell", - "rustls 0.22.4", - "rustls-pki-types", - "rustls-webpki 0.102.4", - "url", - "webpki-roots 0.26.1", -] - [[package]] name = "url" version = "2.5.2" @@ -4631,7 +2684,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -4639,21 +2691,6 @@ name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "serde", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" @@ -4683,27 +2720,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -4729,18 +2751,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -4770,42 +2780,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "webpki-roots" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" -dependencies = [ - "redox_syscall 0.4.1", - "wasite", - "web-sys", -] - [[package]] name = "wild" version = "2.2.1" @@ -4837,7 +2811,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -4846,32 +2820,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.5", -] - [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -4880,22 +2835,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -4904,46 +2844,28 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.5" @@ -4956,73 +2878,30 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" -[[package]] -name = "winnow" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -5058,12 +2937,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - [[package]] name = "zopfli" version = "0.7.4" diff --git a/crates/json-comments-rs/src/lib.rs b/crates/json-comments-rs/src/lib.rs index 737d3894c13..9307951dcf9 100644 --- a/crates/json-comments-rs/src/lib.rs +++ b/crates/json-comments-rs/src/lib.rs @@ -41,10 +41,10 @@ //! # } //! ``` //! -use std::io::ErrorKind; -use std::io::Read; -use std::io::Result; -use std::slice::IterMut; +use std::{ + io::{ErrorKind, Read, Result}, + slice::IterMut, +}; #[derive(Eq, PartialEq, Copy, Clone, Debug)] enum State { @@ -215,7 +215,6 @@ fn strip_buf( /// \n} "); /// /// ``` -#[deprecated] pub fn strip_comments_in_place( s: &mut str, settings: CommentSettings, @@ -390,8 +389,7 @@ fn in_line_comment(c: &mut u8) -> State { #[cfg(test)] mod tests { - use std::io::ErrorKind; - use std::io::Read; + use std::io::{ErrorKind, Read}; use super::*; diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 02fdef07078..2bb04496d80 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,24 +1,21 @@ #![deny(unused_crate_dependencies)] -use std::collections::HashMap; -use std::collections::HashSet; -use std::sync::Arc; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use swc_core::ecma::utils::stack_size::maybe_grow_default; use indexmap::IndexMap; -use swc_core::common::util::take::Take; -use swc_core::common::SourceMap; -use swc_core::common::Span; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::*; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::parser::error::Error; -use swc_core::ecma::parser::lexer::Lexer; -use swc_core::ecma::parser::Parser; -use swc_core::ecma::parser::StringInput; -use swc_core::ecma::visit::Fold; -use swc_core::ecma::visit::FoldWith; +use swc_core::{ + common::{util::take::Take, SourceMap, Span, DUMMY_SP}, + ecma::{ + ast::*, + atoms::{js_word, JsWord}, + parser::{error::Error, lexer::Lexer, Parser, StringInput}, + visit::{Fold, FoldWith}, + }, +}; #[cfg(feature = "napi")] pub mod napi; diff --git a/crates/macros/src/napi.rs b/crates/macros/src/napi.rs index c9ddf25de76..21783ecd015 100644 --- a/crates/macros/src/napi.rs +++ b/crates/macros/src/napi.rs @@ -1,25 +1,15 @@ use std::sync::Arc; -use crossbeam_channel::Receiver; -use crossbeam_channel::Sender; +use crossbeam_channel::{Receiver, Sender}; use indexmap::IndexMap; -use napi::threadsafe_function::ThreadSafeCallContext; -use napi::threadsafe_function::ThreadsafeFunctionCallMode; -use napi::Env; -use napi::JsBoolean; -use napi::JsFunction; -use napi::JsNumber; -use napi::JsObject; -use napi::JsString; -use napi::JsUnknown; -use napi::ValueType; +use napi::{ + threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunctionCallMode}, + Env, JsBoolean, JsFunction, JsNumber, JsObject, JsString, JsUnknown, ValueType, +}; use napi_derive::napi; use swc_core::common::DUMMY_SP; -use crate::JsValue; -use crate::Location; -use crate::MacroCallback; -use crate::MacroError; +use crate::{JsValue, Location, MacroCallback, MacroError}; struct CallMacroMessage { src: String, diff --git a/crates/node-bindings/Cargo.toml b/crates/node-bindings/Cargo.toml index 6bb56c16775..9045cc58f95 100644 --- a/crates/node-bindings/Cargo.toml +++ b/crates/node-bindings/Cargo.toml @@ -7,60 +7,29 @@ edition = "2021" [lib] crate-type = ["cdylib"] -[features] -canary = ["parcel_monitoring/canary"] - [dependencies] -parcel = { path = "../parcel" } -parcel_core = { path = "../parcel_core" } +napi-derive = "2.16.3" parcel-js-swc-core = { path = "../../packages/transformers/js/core" } -parcel_monitoring = { path = "../parcel_monitoring" } parcel-resolver = { path = "../../packages/utils/node-resolver-rs" } -parcel-resolver-old = { path = "../../packages/utils/node-resolver-rs-old" } -parcel_package_manager = { path = "../parcel_package_manager" } -parcel_plugin_transformer_js = { path = "../parcel_plugin_transformer_js" } -parcel_napi_helpers = { path = "../parcel_napi_helpers" } - -anyhow = "1.0.82" dashmap = "5.4.0" -glob = "0.3.1" -log = "0.4.21" -mockall = "0.12.1" -napi-derive = "2.16.3" -num_cpus = "1.16.0" -parking_lot = "0.12" -serde = { version = "1.0.200", features = ["derive"] } -serde_json = "1.0.116" -toml = "0.8.12" -tracing = "0.1.40" -tracing-appender = "0.2.3" -tracing-subscriber = "0.3.18" xxhash-rust = { version = "0.8.2", features = ["xxh3"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -parcel = { path = "../parcel", features = ["nodejs"] } +napi = { version = "2.16.4", features = ["serde-json", "napi4", "napi5"] } parcel-dev-dep-resolver = { path = "../../packages/utils/dev-dep-resolver" } -parcel-dev-dep-resolver-old = { path = "../../packages/utils/dev-dep-resolver-old" } parcel-macros = { path = "../macros", features = ["napi"] } - -crossbeam-channel = "0.5.6" -indexmap = "1.9.2" -libc = "0.2" -mozjpeg-sys = "1.0.0" -napi = { version = "2.16.4", features = ["async", "napi4", "napi5", "serde-json"] } -once_cell = { version = "1.19.0" } oxipng = "8.0.0" +mozjpeg-sys = "2.0.0" +libc = "0.2" rayon = "1.7.0" - -# Crash reporting dependencies +crossbeam-channel = "0.5.6" +indexmap = "1.9.2" [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["custom"], default-features = false } napi = { version = "2.16.4", features = ["serde-json"] } +getrandom = { version = "0.2", features = ["custom"], default-features = false } -[target.'cfg(all(target_os = "macos", not(feature = "canary")))'.dependencies] -# jemallocator is disabled on canary builds to experiment with its performance -# and reliability +[target.'cfg(target_os = "macos")'.dependencies] jemallocator = { version = "0.3.2", features = ["disable_initial_exec_tls"] } [target.'cfg(windows)'.dependencies] diff --git a/crates/node-bindings/src/file_system/file_system_napi.rs b/crates/node-bindings/src/file_system/file_system_napi.rs deleted file mode 100644 index f213f9d51aa..00000000000 --- a/crates/node-bindings/src/file_system/file_system_napi.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::{ - io, - path::{Path, PathBuf}, -}; - -use napi::{Env, JsObject}; -use parcel::file_system::FileSystem; - -use parcel_napi_helpers::js_callable::JsCallable; - -// TODO error handling - -pub struct FileSystemNapi { - canonicalize_fn: JsCallable, - create_directory_fn: JsCallable, - cwd_fn: JsCallable, - read_file_fn: JsCallable, - is_file_fn: JsCallable, - is_dir_fn: JsCallable, -} - -impl FileSystemNapi { - pub fn new(env: &Env, js_file_system: &JsObject) -> napi::Result { - Ok(Self { - canonicalize_fn: JsCallable::new_from_object_prop("canonicalize", &js_file_system)? - .into_unref(env)?, - create_directory_fn: JsCallable::new_from_object_prop("createDirectory", &js_file_system)? - .into_unref(env)?, - cwd_fn: JsCallable::new_from_object_prop("cwd", &js_file_system)?.into_unref(env)?, - read_file_fn: JsCallable::new_from_object_prop("readFile", &js_file_system)? - .into_unref(env)?, - is_file_fn: JsCallable::new_from_object_prop("isFile", &js_file_system)?.into_unref(env)?, - is_dir_fn: JsCallable::new_from_object_prop("isDir", &js_file_system)?.into_unref(env)?, - }) - } -} - -impl FileSystem for FileSystemNapi { - fn canonicalize_base(&self, path: &Path) -> io::Result { - self - .canonicalize_fn - .call_with_return_serde(path.to_path_buf()) - .map_err(|e| io::Error::other(e)) - } - - fn create_directory(&self, path: &Path) -> std::io::Result<()> { - self - .create_directory_fn - .call_with_return_serde(path.to_path_buf()) - .map_err(|e| io::Error::other(e)) - } - - fn cwd(&self) -> io::Result { - self - .cwd_fn - .call_with_return_serde(None::) - .map_err(|e| io::Error::other(e)) - } - - fn read_to_string(&self, path: &Path) -> io::Result { - self - .read_file_fn - .call_with_return_serde((path.to_path_buf(), "utf8")) - .map_err(|e| io::Error::other(e)) - } - - fn is_file(&self, path: &Path) -> bool { - self - .is_file_fn - .call_with_return_serde(path.to_path_buf()) - .expect("TODO handle error case") - } - - fn is_dir(&self, path: &Path) -> bool { - self - .is_dir_fn - .call_with_return_serde(path.to_path_buf()) - .expect("TODO handle error case") - } -} diff --git a/crates/node-bindings/src/file_system/mod.rs b/crates/node-bindings/src/file_system/mod.rs deleted file mode 100644 index 026f8a8ed74..00000000000 --- a/crates/node-bindings/src/file_system/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod file_system_napi; -pub use self::file_system_napi::*; diff --git a/crates/node-bindings/src/hash.rs b/crates/node-bindings/src/hash.rs index d97fb99efc8..dc594cae0f8 100644 --- a/crates/node-bindings/src/hash.rs +++ b/crates/node-bindings/src/hash.rs @@ -4,8 +4,7 @@ use std::hash::Hasher; use napi::bindgen_prelude::Buffer; use napi_derive::napi; -use xxhash_rust::xxh3::xxh3_64; -use xxhash_rust::xxh3::Xxh3; +use xxhash_rust::xxh3::{xxh3_64, Xxh3}; #[napi] pub fn hash_string(s: String) -> String { diff --git a/crates/node-bindings/src/image.rs b/crates/node-bindings/src/image.rs index cd723161a0d..6d54fb52707 100644 --- a/crates/node-bindings/src/image.rs +++ b/crates/node-bindings/src/image.rs @@ -1,17 +1,9 @@ -use std::mem; -use std::ptr; -use std::slice; +use std::{mem, ptr, slice}; use mozjpeg_sys::*; -use napi::bindgen_prelude::*; -use napi::Env; -use napi::Error; -use napi::JsBuffer; -use napi::Result; +use napi::{bindgen_prelude::*, Env, Error, JsBuffer, Result}; use napi_derive::napi; -use oxipng::optimize_from_memory; -use oxipng::Headers; -use oxipng::Options; +use oxipng::{optimize_from_memory, Headers, Options}; #[napi] pub fn optimize_image(kind: String, buf: Buffer, env: Env) -> Result { @@ -121,7 +113,7 @@ unsafe fn create_error_handler() -> jpeg_error_mgr { err } -extern "C" fn unwind_error_exit(cinfo: &mut jpeg_common_struct) { +extern "C-unwind" fn unwind_error_exit(cinfo: &mut jpeg_common_struct) { let message = unsafe { let err = cinfo.err.as_ref().unwrap(); match err.format_message { @@ -137,4 +129,4 @@ extern "C" fn unwind_error_exit(cinfo: &mut jpeg_common_struct) { std::panic::resume_unwind(Box::new(message)) } -extern "C" fn silence_message(_cinfo: &mut jpeg_common_struct, _level: c_int) {} +extern "C-unwind" fn silence_message(_cinfo: &mut jpeg_common_struct, _level: c_int) {} diff --git a/crates/node-bindings/src/lib.rs b/crates/node-bindings/src/lib.rs index 2c5cdd0b965..c91e2cce1e6 100644 --- a/crates/node-bindings/src/lib.rs +++ b/crates/node-bindings/src/lib.rs @@ -13,20 +13,13 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(not(target_arch = "wasm32"))] -mod file_system; - -/// napi versions of `crate::core::requests` #[cfg(not(target_arch = "wasm32"))] mod fs_search; mod hash; #[cfg(not(target_arch = "wasm32"))] mod image; -#[cfg(not(target_arch = "wasm32"))] -mod parcel; mod resolver; -mod resolver_old; mod transformer; #[cfg(target_arch = "wasm32")] diff --git a/crates/node-bindings/src/parcel/mod.rs b/crates/node-bindings/src/parcel/mod.rs deleted file mode 100644 index 9d60bb58383..00000000000 --- a/crates/node-bindings/src/parcel/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod monitoring; -pub mod package_manager_napi; -pub mod parcel; -pub mod worker; diff --git a/crates/node-bindings/src/parcel/monitoring.rs b/crates/node-bindings/src/parcel/monitoring.rs deleted file mode 100644 index 7a6e8bfb6b7..00000000000 --- a/crates/node-bindings/src/parcel/monitoring.rs +++ /dev/null @@ -1,11 +0,0 @@ -use napi_derive::napi; - -#[napi] -pub fn initialize_monitoring() -> napi::Result<()> { - parcel_monitoring::initialize_from_env().map_err(|err| napi::Error::from_reason(err.to_string())) -} - -#[napi] -pub fn close_monitoring() { - parcel_monitoring::close_monitoring(); -} diff --git a/crates/node-bindings/src/parcel/package_manager_napi.rs b/crates/node-bindings/src/parcel/package_manager_napi.rs deleted file mode 100644 index cd94ce80945..00000000000 --- a/crates/node-bindings/src/parcel/package_manager_napi.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::path::Path; - -use anyhow::anyhow; -use napi::{Env, JsObject}; - -use parcel_napi_helpers::js_callable::JsCallable; -use parcel_package_manager::{PackageManager, Resolution}; - -pub struct PackageManagerNapi { - resolve_fn: JsCallable, -} - -impl PackageManagerNapi { - pub fn new(env: &Env, js_file_system: &JsObject) -> napi::Result { - Ok(Self { - resolve_fn: JsCallable::new_from_object_prop_bound("resolveSync", &js_file_system)? - .into_unref(env)?, - }) - } -} - -impl PackageManager for PackageManagerNapi { - fn resolve(&self, specifier: &str, from: &Path) -> anyhow::Result { - self - .resolve_fn - .call_with_return_serde((specifier.to_owned(), from.to_path_buf())) - .map_err(|e| anyhow!(e)) - } -} diff --git a/crates/node-bindings/src/parcel/parcel.rs b/crates/node-bindings/src/parcel/parcel.rs deleted file mode 100644 index 76e5b09705c..00000000000 --- a/crates/node-bindings/src/parcel/parcel.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::sync::mpsc::channel; -use std::sync::mpsc::Sender; -use std::sync::Arc; -use std::thread; - -use napi::Env; -use napi::JsFunction; -use napi::JsObject; -use napi::JsUnknown; -use napi_derive::napi; - -use parcel::file_system::FileSystemRef; -use parcel::rpc::nodejs::NodejsWorker; -use parcel::rpc::nodejs::RpcHostNodejs; -use parcel::rpc::RpcHostRef; -use parcel::Parcel; -use parcel_core::types::ParcelOptions; -use parcel_napi_helpers::JsTransferable; -use parcel_package_manager::PackageManagerRef; - -use crate::file_system::FileSystemNapi; - -use super::package_manager_napi::PackageManagerNapi; - -#[napi(object)] -pub struct ParcelNapiBuildOptions { - pub register_worker: JsFunction, -} - -#[napi(object)] -pub struct ParcelNapiBuildResult {} - -#[napi(object)] -pub struct ParcelNapiOptions { - pub fs: Option, - pub node_workers: Option, - pub options: JsObject, - pub package_manager: Option, - pub threads: Option, -} - -#[napi] -pub struct ParcelNapi { - pub node_worker_count: u32, - fs: Option, - options: ParcelOptions, - package_manager: Option, - rpc: Option, - tx_worker: Sender, -} - -#[napi] -impl ParcelNapi { - #[napi(constructor)] - pub fn new(napi_options: ParcelNapiOptions, env: Env) -> napi::Result { - let thread_id = std::thread::current().id(); - tracing::trace!(?thread_id, "parcel-napi initialize"); - - // Wrap the JavaScript-supplied FileSystem - let fs: Option = if let Some(fs) = napi_options.fs { - Some(Arc::new(FileSystemNapi::new(&env, &fs)?)) - } else { - None - }; - - let package_manager: Option = if let Some(pm) = napi_options.package_manager - { - Some(Arc::new(PackageManagerNapi::new(&env, &pm)?)) - } else { - None - }; - - // Assign Rust thread count from JavaScript - let threads = napi_options - .threads - .map(|t| t as usize) - .unwrap_or_else(|| num_cpus::get()); - - // Set up Nodejs plugin bindings - let node_worker_count = napi_options - .node_workers - .map(|w| w as usize) - .unwrap_or_else(|| threads); - - let (tx_worker, rx_worker) = channel::(); - let rpc_host_nodejs = RpcHostNodejs::new(node_worker_count, rx_worker)?; - let rpc = Some::(Arc::new(rpc_host_nodejs)); - - Ok(Self { - fs, - node_worker_count: node_worker_count as u32, - options: env.from_js_value(napi_options.options)?, - package_manager, - rpc, - tx_worker, - }) - } - - #[napi] - pub fn build(&self, env: Env, options: ParcelNapiBuildOptions) -> napi::Result { - let (deferred, promise) = env.create_deferred()?; - - self.register_workers(&options)?; - - // Both the parcel initialization and build must be run a dedicated system thread so that - // the napi threadsafe functions do not panic - thread::spawn({ - let fs = self.fs.clone(); - let options = self.options.clone(); - let package_manager = self.package_manager.clone(); - let rpc = self.rpc.clone(); - - move || { - let parcel = Parcel::new(fs, options, package_manager, rpc); - let to_napi_error = |error| napi::Error::from_reason(format!("{:?}", error)); - - match parcel { - Err(error) => deferred.reject(to_napi_error(error)), - Ok(mut parcel) => match parcel.build() { - Ok(build_result) => deferred.resolve(move |env| env.to_js_value(&build_result)), - Err(error) => deferred.reject(to_napi_error(error)), - }, - } - } - }); - - Ok(promise) - } - - #[napi] - pub fn build_asset_graph( - &self, - env: Env, - options: ParcelNapiBuildOptions, - ) -> napi::Result { - let (deferred, promise) = env.create_deferred()?; - - self.register_workers(&options)?; - - // Both the parcel initialisation and build must be run a dedicated system thread so that - // the napi threadsafe functions do not panic - thread::spawn({ - let fs = self.fs.clone(); - let options = self.options.clone(); - let package_manager = self.package_manager.clone(); - let rpc = self.rpc.clone(); - - move || { - let parcel = Parcel::new(fs, options, package_manager, rpc); - let to_napi_error = |error| napi::Error::from_reason(format!("{:?}", error)); - - match parcel { - Err(error) => deferred.reject(to_napi_error(error)), - Ok(mut parcel) => match parcel.build_asset_graph() { - Ok(asset_graph) => deferred.resolve(move |env| env.to_js_value(&asset_graph)), - Err(error) => deferred.reject(to_napi_error(error)), - }, - } - } - }); - - Ok(promise) - } - - fn register_workers(&self, options: &ParcelNapiBuildOptions) -> napi::Result<()> { - for _ in 0..self.node_worker_count { - let transferable = JsTransferable::new(self.tx_worker.clone()); - - options - .register_worker - .call1::>, JsUnknown>(transferable)?; - } - - Ok(()) - } -} diff --git a/crates/node-bindings/src/parcel/worker.rs b/crates/node-bindings/src/parcel/worker.rs deleted file mode 100644 index 71e9a82fa2f..00000000000 --- a/crates/node-bindings/src/parcel/worker.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::sync::mpsc::Sender; - -use napi::{Env, JsObject, JsUndefined}; -use napi_derive::napi; -use parcel::rpc::nodejs::NodejsWorker; -use parcel_napi_helpers::JsTransferable; - -/// This function is run in the Nodejs worker context upon initialization -/// to notify the main thread that a Nodejs worker thread has started -/// -/// A Rust channel is transferred to the worker via JavaScript `worker.postMessage`. -/// The worker then calls `register_worker`, supplying it with an object containing -/// callbacks. -/// -/// The callbacks are later called from the main thread to send work to the worker. -/// -/// |-------------| --- Init channel ----> |-------------------| -/// | Main Thread | | Worker Thread (n) | -/// |-------------| <-- Worker wrapper --- |-------------------| -/// -/// **Later During Build** -/// -/// -- Resolver.resolve --> -/// <- DependencyResult --- -/// -/// -- Transf.transform --> -/// <--- Array ----- -#[napi] -pub fn register_worker( - env: Env, - channel: JsTransferable>, - worker: JsObject, -) -> napi::Result { - let worker = NodejsWorker::new(worker)?; - let tx_worker = channel.take()?; - if tx_worker.send(worker).is_err() { - return Err(napi::Error::from_reason("Unable to register worker")); - } - env.get_undefined() -} diff --git a/crates/node-bindings/src/resolver.rs b/crates/node-bindings/src/resolver.rs index bbdc14db205..f1285e7a0fa 100644 --- a/crates/node-bindings/src/resolver.rs +++ b/crates/node-bindings/src/resolver.rs @@ -1,38 +1,25 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; #[cfg(not(target_arch = "wasm32"))] use std::sync::atomic::Ordering; -use std::sync::Arc; - -use napi::bindgen_prelude::Either3; -use napi::Env; -use napi::JsBoolean; -use napi::JsBuffer; -use napi::JsFunction; -use napi::JsObject; -use napi::JsString; -use napi::JsUnknown; -use napi::Ref; -use napi::Result; +use std::{ + borrow::Cow, + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; + +use napi::{ + bindgen_prelude::Either3, Env, JsBoolean, JsBuffer, JsFunction, JsObject, JsString, JsUnknown, + Ref, Result, +}; use napi_derive::napi; -use parcel::file_system::{FileSystemRealPathCache, FileSystemRef}; -use parcel_resolver::ExportsCondition; -use parcel_resolver::Extensions; -use parcel_resolver::Fields; -use parcel_resolver::FileCreateInvalidation; -use parcel_resolver::FileSystem; -use parcel_resolver::Flags; -use parcel_resolver::IncludeNodeModules; -use parcel_resolver::Invalidations; -use parcel_resolver::ModuleType; #[cfg(not(target_arch = "wasm32"))] use parcel_resolver::OsFileSystem; -use parcel_resolver::Resolution; -use parcel_resolver::ResolverError; -use parcel_resolver::SpecifierType; +use parcel_resolver::{ + ExportsCondition, Extensions, Fields, FileCreateInvalidation, FileSystem, + FileSystemRealPathCache, Flags, IncludeNodeModules, Invalidations, ModuleType, Resolution, + ResolverError, SpecifierType, +}; type NapiSideEffectsVariants = Either3, HashMap>; @@ -204,7 +191,7 @@ impl Resolver { pub fn new(project_root: String, options: JsResolverOptions, env: Env) -> Result { let mut supports_async = false; #[cfg(not(target_arch = "wasm32"))] - let fs: FileSystemRef = if let Some(fs) = options.fs { + let fs: Arc = if let Some(fs) = options.fs { Arc::new(JsFileSystem { canonicalize: FunctionRef::new(env, fs.canonicalize)?, read: FunctionRef::new(env, fs.read)?, @@ -464,29 +451,25 @@ fn convert_invalidations( Vec, Vec>, ) { - let invalidate_on_file_change = invalidations.invalidate_on_file_change.read().unwrap(); - let invalidate_on_file_change = invalidate_on_file_change - .iter() + let invalidate_on_file_change = invalidations + .invalidate_on_file_change + .into_iter() .map(|p| p.to_string_lossy().into_owned()) .collect(); let invalidate_on_file_create = invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() + .into_iter() .map(|i| match i { FileCreateInvalidation::Path(p) => Either3::A(FilePathCreateInvalidation { file_path: p.to_string_lossy().into_owned(), }), FileCreateInvalidation::FileName { file_name, above } => { Either3::B(FileNameCreateInvalidation { - file_name: file_name.clone(), + file_name, above_file_path: above.to_string_lossy().into_owned(), }) } - FileCreateInvalidation::Glob(glob) => { - Either3::C(GlobCreateInvalidation { glob: glob.clone() }) - } + FileCreateInvalidation::Glob(glob) => Either3::C(GlobCreateInvalidation { glob }), }) .collect(); (invalidate_on_file_change, invalidate_on_file_create) diff --git a/crates/node-bindings/src/resolver_old.rs b/crates/node-bindings/src/resolver_old.rs deleted file mode 100644 index 01d29954d63..00000000000 --- a/crates/node-bindings/src/resolver_old.rs +++ /dev/null @@ -1,522 +0,0 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; -#[cfg(not(target_arch = "wasm32"))] -use std::sync::atomic::Ordering; -use std::sync::Arc; - -use napi::bindgen_prelude::Either3; -use napi::Env; -use napi::JsBoolean; -use napi::JsBuffer; -use napi::JsFunction; -use napi::JsObject; -use napi::JsString; -use napi::JsUnknown; -use napi::Ref; -use napi::Result; -use napi_derive::napi; - -use parcel::file_system::{FileSystemRealPathCache, FileSystemRef}; -use parcel_resolver_old::ExportsCondition; -use parcel_resolver_old::Extensions; -use parcel_resolver_old::Fields; -use parcel_resolver_old::FileCreateInvalidation; -use parcel_resolver_old::FileSystem; -use parcel_resolver_old::Flags; -use parcel_resolver_old::IncludeNodeModules; -use parcel_resolver_old::Invalidations; -use parcel_resolver_old::ModuleType; -#[cfg(not(target_arch = "wasm32"))] -use parcel_resolver_old::OsFileSystem; -use parcel_resolver_old::Resolution; -use parcel_resolver_old::ResolverError; -use parcel_resolver_old::SpecifierType; - -type NapiSideEffectsVariants = Either3, HashMap>; - -#[napi(object)] -pub struct JsFileSystemOptionsOld { - pub canonicalize: JsFunction, - pub read: JsFunction, - pub is_file: JsFunction, - pub is_dir: JsFunction, - pub include_node_modules: Option, -} - -#[napi(object, js_name = "FileSystemOld")] -pub struct JsResolverOptionsOld { - pub fs: Option, - pub include_node_modules: Option, - pub conditions: Option, - pub module_dir_resolver: Option, - pub mode: u8, - pub entries: Option, - pub extensions: Option>, - pub package_exports: bool, - pub typescript: Option, -} - -pub struct FunctionRef { - env: Env, - reference: Ref<()>, -} - -// We don't currently call functions from multiple threads, but we'll need to change this when we do. -unsafe impl Send for FunctionRef {} -unsafe impl Sync for FunctionRef {} - -impl FunctionRef { - fn new(env: Env, f: JsFunction) -> napi::Result { - Ok(Self { - env, - reference: env.create_reference(f)?, - }) - } - - fn get(&self) -> napi::Result { - self.env.get_reference_value(&self.reference) - } -} - -impl Drop for FunctionRef { - fn drop(&mut self) { - drop(self.reference.unref(self.env)) - } -} - -pub struct JsFileSystem { - pub canonicalize: FunctionRef, - pub read: FunctionRef, - pub is_file: FunctionRef, - pub is_dir: FunctionRef, -} - -impl FileSystem for JsFileSystem { - fn canonicalize( - &self, - path: &Path, - _cache: &FileSystemRealPathCache, - ) -> std::io::Result { - let canonicalize = || -> napi::Result<_> { - let path = path.to_string_lossy(); - let path = self.canonicalize.env.create_string(path.as_ref())?; - let res: JsString = self.canonicalize.get()?.call(None, &[path])?.try_into()?; - let utf8 = res.into_utf8()?; - Ok(utf8.into_owned()?.into()) - }; - - canonicalize().map_err(|err| std::io::Error::new(std::io::ErrorKind::NotFound, err.to_string())) - } - - fn read_to_string(&self, path: &Path) -> std::io::Result { - let read = || -> napi::Result<_> { - let path = path.to_string_lossy(); - let path = self.read.env.create_string(path.as_ref())?; - let res: JsBuffer = self.read.get()?.call(None, &[path])?.try_into()?; - let value = res.into_value()?; - Ok(unsafe { String::from_utf8_unchecked(value.to_vec()) }) - }; - - read().map_err(|err| std::io::Error::new(std::io::ErrorKind::NotFound, err.to_string())) - } - - fn is_file(&self, path: &Path) -> bool { - let is_file = || -> napi::Result<_> { - let path = path.to_string_lossy(); - let p = self.is_file.env.create_string(path.as_ref())?; - let res: JsBoolean = self.is_file.get()?.call(None, &[p])?.try_into()?; - res.get_value() - }; - - is_file().unwrap_or(false) - } - - fn is_dir(&self, path: &Path) -> bool { - let is_dir = || -> napi::Result<_> { - let path = path.to_string_lossy(); - let path = self.is_dir.env.create_string(path.as_ref())?; - let res: JsBoolean = self.is_dir.get()?.call(None, &[path])?.try_into()?; - res.get_value() - }; - - is_dir().unwrap_or(false) - } -} - -#[napi(object)] -pub struct ResolveOptionsOld { - pub filename: String, - pub specifier_type: String, - pub parent: String, - pub package_conditions: Option>, -} - -#[napi(object)] -pub struct FilePathCreateInvalidationOld { - pub file_path: String, -} - -#[napi(object)] -pub struct FileNameCreateInvalidationOld { - pub file_name: String, - pub above_file_path: String, -} - -#[napi(object)] -pub struct GlobCreateInvalidationOld { - pub glob: String, -} - -#[napi(object)] -pub struct ResolveResultOld { - pub resolution: JsUnknown, - pub invalidate_on_file_change: Vec, - pub invalidate_on_file_create: Vec< - Either3< - FilePathCreateInvalidationOld, - FileNameCreateInvalidationOld, - GlobCreateInvalidationOld, - >, - >, - pub query: Option, - pub side_effects: bool, - pub error: JsUnknown, - pub module_type: u8, -} - -#[napi(object)] -pub struct JsInvalidationsOld { - pub invalidate_on_file_change: Vec, - pub invalidate_on_file_create: Vec< - Either3< - FilePathCreateInvalidationOld, - FileNameCreateInvalidationOld, - GlobCreateInvalidationOld, - >, - >, - pub invalidate_on_startup: bool, -} - -#[napi] -pub struct ResolverOld { - mode: u8, - resolver: parcel_resolver_old::Resolver<'static>, - #[cfg(not(target_arch = "wasm32"))] - invalidations_cache: parcel_dev_dep_resolver_old::Cache, - supports_async: bool, -} - -#[napi] -impl ResolverOld { - #[napi(constructor)] - pub fn new(project_root: String, options: JsResolverOptionsOld, env: Env) -> Result { - let mut supports_async = false; - #[cfg(not(target_arch = "wasm32"))] - let fs: FileSystemRef = if let Some(fs) = options.fs { - Arc::new(JsFileSystem { - canonicalize: FunctionRef::new(env, fs.canonicalize)?, - read: FunctionRef::new(env, fs.read)?, - is_file: FunctionRef::new(env, fs.is_file)?, - is_dir: FunctionRef::new(env, fs.is_dir)?, - }) - } else { - supports_async = true; - Arc::new(OsFileSystem) - }; - #[cfg(target_arch = "wasm32")] - let fs = { - let fsjs = options.fs.unwrap(); - Arc::new(JsFileSystem { - canonicalize: FunctionRef::new(env, fsjs.canonicalize)?, - read: FunctionRef::new(env, fsjs.read)?, - is_file: FunctionRef::new(env, fsjs.is_file)?, - is_dir: FunctionRef::new(env, fsjs.is_dir)?, - }) - }; - - let mut resolver = match options.mode { - 1 => parcel_resolver_old::Resolver::parcel( - Cow::Owned(project_root.into()), - parcel_resolver_old::CacheCow::Owned(parcel_resolver_old::Cache::new(fs)), - ), - 2 => parcel_resolver_old::Resolver::node( - Cow::Owned(project_root.into()), - parcel_resolver_old::CacheCow::Owned(parcel_resolver_old::Cache::new(fs)), - ), - _ => return Err(napi::Error::new(napi::Status::InvalidArg, "Invalid mode")), - }; - - if let Some(include_node_modules) = options.include_node_modules { - resolver.include_node_modules = Cow::Owned(match include_node_modules { - Either3::A(b) => IncludeNodeModules::Bool(b), - Either3::B(v) => IncludeNodeModules::Array(v), - Either3::C(v) => IncludeNodeModules::Map(v), - }); - } - - if let Some(conditions) = options.conditions { - resolver.conditions = ExportsCondition::from_bits_truncate(conditions); - } - - if let Some(entries) = options.entries { - resolver.entries = Fields::from_bits_truncate(entries); - } - - if let Some(extensions) = options.extensions { - resolver.extensions = Extensions::Owned(extensions); - } - - resolver.flags.set(Flags::EXPORTS, options.package_exports); - - if matches!(options.typescript, Some(true)) { - resolver.flags |= Flags::TYPESCRIPT; - } - - if let Some(module_dir_resolver) = options.module_dir_resolver { - let module_dir_resolver = FunctionRef::new(env, module_dir_resolver)?; - resolver.module_dir_resolver = Some(Arc::new(move |module: &str, from: &Path| { - let call = |module: &str| -> napi::Result { - let env = module_dir_resolver.env; - let s = env.create_string(module)?; - let f = env.create_string(from.to_string_lossy().as_ref())?; - let res: JsString = module_dir_resolver.get()?.call(None, &[s, f])?.try_into()?; - let utf8 = res.into_utf8()?; - Ok(utf8.into_owned()?.into()) - }; - - let r = call(module); - r.map_err(|_| ResolverError::ModuleNotFound { - module: module.to_owned(), - }) - })); - } - - Ok(Self { - mode: options.mode, - resolver, - supports_async, - #[cfg(not(target_arch = "wasm32"))] - invalidations_cache: Default::default(), - }) - } - - fn resolve_internal( - &self, - options: ResolveOptionsOld, - ) -> napi::Result<(parcel_resolver_old::ResolveResult, bool, u8)> { - let mut res = self.resolver.resolve_with_options( - &options.filename, - Path::new(&options.parent), - match options.specifier_type.as_ref() { - "esm" => SpecifierType::Esm, - "commonjs" => SpecifierType::Cjs, - "url" => SpecifierType::Url, - "custom" => { - return Err(napi::Error::new( - napi::Status::InvalidArg, - "Unsupported specifier type: custom", - )) - } - _ => { - return Err(napi::Error::new( - napi::Status::InvalidArg, - format!("Invalid specifier type: {}", options.specifier_type), - )) - } - }, - if let Some(conditions) = options.package_conditions { - get_resolve_options(conditions) - } else { - Default::default() - }, - ); - - let side_effects = if let Ok((Resolution::Path(p), _)) = &res.result { - match self.resolver.resolve_side_effects(p, &res.invalidations) { - Ok(side_effects) => side_effects, - Err(err) => { - res.result = Err(err); - true - } - } - } else { - true - }; - - let mut module_type = 0; - - if self.mode == 2 { - if let Ok((Resolution::Path(p), _)) = &res.result { - module_type = match self.resolver.resolve_module_type(p, &res.invalidations) { - Ok(t) => match t { - ModuleType::CommonJs | ModuleType::Json => 1, - ModuleType::Module => 2, - }, - Err(err) => { - res.result = Err(err); - 0 - } - } - } - } - - Ok((res, side_effects, module_type)) - } - - fn resolve_result_to_js( - &self, - env: Env, - res: parcel_resolver_old::ResolveResult, - side_effects: bool, - module_type: u8, - ) -> napi::Result { - let (invalidate_on_file_change, invalidate_on_file_create) = - convert_invalidations(res.invalidations); - - match res.result { - Ok((res, query)) => Ok(ResolveResultOld { - resolution: env.to_js_value(&res)?, - invalidate_on_file_change, - invalidate_on_file_create, - side_effects, - query, - error: env.get_undefined()?.into_unknown(), - module_type, - }), - Err(err) => Ok(ResolveResultOld { - resolution: env.get_undefined()?.into_unknown(), - invalidate_on_file_change, - invalidate_on_file_create, - side_effects: true, - query: None, - error: env.to_js_value(&err)?, - module_type: 0, - }), - } - } - - #[napi] - pub fn resolve(&self, options: ResolveOptionsOld, env: Env) -> Result { - let (res, side_effects, module_type) = self.resolve_internal(options)?; - self.resolve_result_to_js(env, res, side_effects, module_type) - } - - #[cfg(target_arch = "wasm32")] - #[napi] - pub fn resolve_async(&'static self) -> Result { - panic!("resolveAsync() is not supported in Wasm builds") - } - - #[cfg(not(target_arch = "wasm32"))] - #[napi] - pub fn resolve_async(&'static self, options: ResolveOptionsOld, env: Env) -> Result { - let (deferred, promise) = env.create_deferred()?; - let resolver = &self.resolver; - - if !self.supports_async || resolver.module_dir_resolver.is_some() { - return Err(napi::Error::new( - napi::Status::GenericFailure, - "resolveAsync does not support custom fs or module_dir_resolver", - )); - } - - rayon::spawn(move || { - let (res, side_effects, module_type) = match self.resolve_internal(options) { - Ok(r) => r, - Err(e) => return deferred.reject(e), - }; - - deferred.resolve(move |env| self.resolve_result_to_js(env, res, side_effects, module_type)); - }); - - Ok(promise) - } - - #[cfg(target_arch = "wasm32")] - #[napi] - pub fn get_invalidations(&self, _path: String) -> napi::Result { - panic!("getInvalidations() is not supported in Wasm builds") - } - - #[cfg(not(target_arch = "wasm32"))] - #[napi] - pub fn get_invalidations(&self, path: String) -> napi::Result { - let path = Path::new(&path); - match parcel_dev_dep_resolver_old::build_esm_graph( - path, - &self.resolver.project_root, - &self.resolver.cache, - &self.invalidations_cache, - ) { - Ok(invalidations) => { - let invalidate_on_startup = invalidations.invalidate_on_startup.load(Ordering::Relaxed); - let (invalidate_on_file_change, invalidate_on_file_create) = - convert_invalidations(invalidations); - Ok(JsInvalidationsOld { - invalidate_on_file_change, - invalidate_on_file_create, - invalidate_on_startup, - }) - } - Err(_) => Err(napi::Error::new( - napi::Status::GenericFailure, - "Failed to resolve invalidations", - )), - } - } -} - -fn convert_invalidations( - invalidations: Invalidations, -) -> ( - Vec, - Vec< - Either3< - FilePathCreateInvalidationOld, - FileNameCreateInvalidationOld, - GlobCreateInvalidationOld, - >, - >, -) { - let invalidate_on_file_change = invalidations - .invalidate_on_file_change - .into_iter() - .map(|p| p.to_string_lossy().into_owned()) - .collect(); - let invalidate_on_file_create = invalidations - .invalidate_on_file_create - .into_iter() - .map(|i| match i { - FileCreateInvalidation::Path(p) => Either3::A(FilePathCreateInvalidationOld { - file_path: p.to_string_lossy().into_owned(), - }), - FileCreateInvalidation::FileName { file_name, above } => { - Either3::B(FileNameCreateInvalidationOld { - file_name, - above_file_path: above.to_string_lossy().into_owned(), - }) - } - FileCreateInvalidation::Glob(glob) => Either3::C(GlobCreateInvalidationOld { glob }), - }) - .collect(); - (invalidate_on_file_change, invalidate_on_file_create) -} - -fn get_resolve_options(mut custom_conditions: Vec) -> parcel_resolver_old::ResolveOptions { - let mut conditions = ExportsCondition::empty(); - custom_conditions.retain(|condition| { - if let Ok(cond) = ExportsCondition::try_from(condition.as_ref()) { - conditions |= cond; - false - } else { - true - } - }); - - parcel_resolver_old::ResolveOptions { - conditions, - custom_conditions, - } -} diff --git a/crates/node-bindings/src/transformer.rs b/crates/node-bindings/src/transformer.rs index 44b9f32a8d1..972f948be87 100644 --- a/crates/node-bindings/src/transformer.rs +++ b/crates/node-bindings/src/transformer.rs @@ -1,6 +1,4 @@ -use napi::Env; -use napi::JsObject; -use napi::JsUnknown; +use napi::{Env, JsObject, JsUnknown}; use napi_derive::napi; #[napi] diff --git a/crates/parcel/Cargo.toml b/crates/parcel/Cargo.toml deleted file mode 100644 index c9f8b3fcfb2..00000000000 --- a/crates/parcel/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "parcel" -version = "0.1.0" -edition = "2021" -description = "Parcel Bundler" - -[features] -nodejs = ["parcel_plugin_rpc/nodejs"] - -[dependencies] -parcel_config = { path = "../parcel_config" } -parcel_core = { path = "../parcel_core" } -parcel_filesystem = { path = "../parcel_filesystem" } -parcel_package_manager = { path = "../parcel_package_manager" } -parcel_plugin_resolver = { path = "../parcel_plugin_resolver" } -parcel_plugin_transformer_js = { path = "../parcel_plugin_transformer_js" } -parcel_plugin_rpc = { path = "../parcel_plugin_rpc" } -parcel-resolver = { path = "../../packages/utils/node-resolver-rs" } - -anyhow = "1.0.82" -dyn-hash = "0.x" -num_cpus = "1.16.0" -pathdiff = "0.2.1" -petgraph = "0.x" -rand = "0.8.5" -rayon = "1.10.0" -regex = "1.10.5" -serde = { version = "1.0.200", features = ["derive"] } -serde-bool = "0.1.3" -serde_json = "1.0.116" -tracing = "0.1.40" -tracing-subscriber = "0.3.18" -xxhash-rust = { version = "0.8.2", features = ["xxh3"] } - -[dev-dependencies] -mockall = "0.12.1" diff --git a/crates/parcel/src/lib.rs b/crates/parcel/src/lib.rs deleted file mode 100644 index 283c2613b5b..00000000000 --- a/crates/parcel/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub use parcel::*; -pub use parcel_filesystem as file_system; -pub use parcel_plugin_rpc as rpc; - -pub mod parcel; -pub(crate) mod request_tracker; - -mod plugins; -mod project_root; -mod requests; - -#[cfg(test)] -mod test_utils; diff --git a/crates/parcel/src/parcel.rs b/crates/parcel/src/parcel.rs deleted file mode 100644 index 690401fd286..00000000000 --- a/crates/parcel/src/parcel.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use parcel_config::parcel_rc_config_loader::LoadConfigOptions; -use parcel_config::parcel_rc_config_loader::ParcelRcConfigLoader; -use parcel_core::asset_graph::AssetGraph; -use parcel_core::config_loader::ConfigLoader; -use parcel_core::plugin::PluginContext; -use parcel_core::plugin::PluginLogger; -use parcel_core::plugin::PluginOptions; -use parcel_core::plugin::ReporterEvent; -use parcel_core::types::ParcelOptions; -use parcel_filesystem::os_file_system::OsFileSystem; -use parcel_filesystem::FileSystemRef; -use parcel_package_manager::NodePackageManager; -use parcel_package_manager::PackageManagerRef; -use parcel_plugin_rpc::RpcHostRef; -use parcel_plugin_rpc::RpcWorkerRef; - -use crate::plugins::config_plugins::ConfigPlugins; -use crate::plugins::PluginsRef; -use crate::project_root::infer_project_root; -use crate::request_tracker::RequestTracker; -use crate::requests::AssetGraphRequest; -use crate::requests::RequestResult; - -#[derive(Clone)] -struct ParcelState { - config: Arc, - plugins: PluginsRef, -} - -pub struct Parcel { - pub fs: FileSystemRef, - pub options: ParcelOptions, - pub package_manager: PackageManagerRef, - pub project_root: PathBuf, - pub rpc: Option, - state: Option, -} - -impl Parcel { - pub fn new( - fs: Option, - options: ParcelOptions, - package_manager: Option, - rpc: Option, - ) -> Result { - let fs = fs.unwrap_or_else(|| Arc::new(OsFileSystem::default())); - let project_root = infer_project_root(Arc::clone(&fs), options.entries.clone())?; - - let package_manager = package_manager - .unwrap_or_else(|| Arc::new(NodePackageManager::new(project_root.clone(), fs.clone()))); - - Ok(Self { - fs, - options, - package_manager, - project_root, - rpc, - state: None, - }) - } -} - -pub struct BuildResult; - -impl Parcel { - fn state(&mut self) -> anyhow::Result { - if let Some(state) = self.state.clone() { - return Ok(state); - } - - let mut _rpc_connection = None::; - - if let Some(rpc_host) = &self.rpc { - _rpc_connection = Some(rpc_host.start()?); - } - - let (config, _files) = - ParcelRcConfigLoader::new(Arc::clone(&self.fs), Arc::clone(&self.package_manager)).load( - &self.project_root, - LoadConfigOptions { - additional_reporters: vec![], // TODO - config: self.options.config.as_deref(), - fallback_config: self.options.fallback_config.as_deref(), - }, - )?; - - let config_loader = Arc::new(ConfigLoader { - fs: Arc::clone(&self.fs), - project_root: self.project_root.clone(), - search_path: self.project_root.join("index"), - }); - - let plugins = Arc::new(ConfigPlugins::new( - config, - PluginContext { - config: Arc::clone(&config_loader), - file_system: self.fs.clone(), - options: Arc::new(PluginOptions { - core_path: self.options.core_path.clone(), - env: self.options.env.clone(), - log_level: self.options.log_level.clone(), - mode: self.options.mode.clone(), - project_root: self.project_root.clone(), - }), - // TODO Initialise actual logger - logger: PluginLogger::default(), - }, - )); - - let state = ParcelState { - config: config_loader, - plugins, - }; - - self.state = Some(state.clone()); - - Ok(state) - } - - pub fn build(&mut self) -> anyhow::Result<()> { - let ParcelState { config, plugins } = self.state()?; - - plugins.reporter().report(&ReporterEvent::BuildStart)?; - - let mut _request_tracker = RequestTracker::new( - config.clone(), - self.fs.clone(), - Arc::new(self.options.clone()), - plugins.clone(), - self.project_root.clone(), - ); - - Ok(()) - } - - pub fn build_asset_graph(&mut self) -> anyhow::Result { - let ParcelState { config, plugins } = self.state()?; - - let mut request_tracker = RequestTracker::new( - config.clone(), - self.fs.clone(), - Arc::new(self.options.clone()), - plugins.clone(), - self.project_root.clone(), - ); - - let request_result = request_tracker.run_request(AssetGraphRequest {})?; - - let asset_graph = match request_result { - RequestResult::AssetGraph(result) => result.graph, - _ => panic!("TODO"), - }; - - Ok(asset_graph) - } -} diff --git a/crates/parcel/src/plugins.rs b/crates/parcel/src/plugins.rs deleted file mode 100644 index fb533947fdb..00000000000 --- a/crates/parcel/src/plugins.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::fmt::Debug; -use std::path::Path; -use std::sync::Arc; -use std::u64; - -#[cfg(test)] -use mockall::automock; -use parcel_core::plugin::BundlerPlugin; -use parcel_core::plugin::CompressorPlugin; -use parcel_core::plugin::NamerPlugin; -use parcel_core::plugin::OptimizerPlugin; -use parcel_core::plugin::PackagerPlugin; -use parcel_core::plugin::ReporterPlugin; -use parcel_core::plugin::ResolverPlugin; -use parcel_core::plugin::RuntimePlugin; -use parcel_core::plugin::TransformerPlugin; -use parcel_core::plugin::ValidatorPlugin; - -pub type PluginsRef = Arc; - -pub mod config_plugins; - -#[cfg_attr(test, automock)] -pub trait Plugins { - #[allow(unused)] - fn bundler(&self) -> Result, anyhow::Error>; - #[allow(unused)] - fn compressors(&self, path: &Path) -> Result>, anyhow::Error>; - fn named_pipelines(&self) -> Vec; - #[allow(unused)] - fn namers(&self) -> Result>, anyhow::Error>; - #[allow(unused)] - fn optimizers( - &self, - path: &Path, - pipeline: Option, - ) -> Result>, anyhow::Error>; - #[allow(unused)] - fn packager(&self, path: &Path) -> Result, anyhow::Error>; - fn reporter(&self) -> Arc; - fn resolvers(&self) -> Result>, anyhow::Error>; - #[allow(unused)] - fn runtimes(&self) -> Result>, anyhow::Error>; - fn transformers( - &self, - path: &Path, - pipeline: Option, - ) -> Result; - #[allow(unused)] - fn validators(&self, _path: &Path) -> Result>, anyhow::Error>; -} - -pub struct TransformerPipeline { - pub transformers: Vec>, - hash: u64, -} - -impl TransformerPipeline { - pub fn hash(&self) -> u64 { - self.hash - } -} - -impl Debug for TransformerPipeline { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TransformerPipeline") - .field("transformers", &self.transformers) - .finish() - } -} diff --git a/crates/parcel/src/plugins/config_plugins.rs b/crates/parcel/src/plugins/config_plugins.rs deleted file mode 100644 index bdc2551688f..00000000000 --- a/crates/parcel/src/plugins/config_plugins.rs +++ /dev/null @@ -1,325 +0,0 @@ -use std::hash::Hash; -use std::hash::Hasher; -use std::path::Path; -use std::sync::Arc; - -use parcel_config::map::NamedPattern; -use parcel_config::ParcelConfig; -use parcel_core::diagnostic_error; -use parcel_core::plugin::composite_reporter_plugin::CompositeReporterPlugin; -use parcel_core::plugin::BundlerPlugin; -use parcel_core::plugin::CompressorPlugin; -use parcel_core::plugin::NamerPlugin; -use parcel_core::plugin::OptimizerPlugin; -use parcel_core::plugin::PackagerPlugin; -use parcel_core::plugin::PluginContext; -use parcel_core::plugin::ReporterPlugin; -use parcel_core::plugin::ResolverPlugin; -use parcel_core::plugin::RuntimePlugin; -use parcel_core::plugin::TransformerPlugin; -use parcel_core::plugin::ValidatorPlugin; -use parcel_plugin_resolver::ParcelResolver; -use parcel_plugin_rpc::plugin::RpcBundlerPlugin; -use parcel_plugin_rpc::plugin::RpcCompressorPlugin; -use parcel_plugin_rpc::plugin::RpcNamerPlugin; -use parcel_plugin_rpc::plugin::RpcOptimizerPlugin; -use parcel_plugin_rpc::plugin::RpcPackagerPlugin; -use parcel_plugin_rpc::plugin::RpcReporterPlugin; -use parcel_plugin_rpc::plugin::RpcResolverPlugin; -use parcel_plugin_rpc::plugin::RpcRuntimePlugin; -use parcel_plugin_rpc::plugin::RpcTransformerPlugin; -use parcel_plugin_transformer_js::ParcelJsTransformerPlugin; - -use super::Plugins; -use super::TransformerPipeline; - -/// Loads plugins based on the Parcel config -pub struct ConfigPlugins { - /// The Parcel config that determines what plugins will be loaded - config: ParcelConfig, - - /// Dependencies available to all plugin types - ctx: PluginContext, - - /// A reporter that runs all reporter plugins - reporter: Arc, -} - -impl ConfigPlugins { - pub fn new(config: ParcelConfig, ctx: PluginContext) -> Self { - let mut reporters: Vec> = Vec::new(); - - for reporter in config.reporters.iter() { - reporters.push(Box::new(RpcReporterPlugin::new(&ctx, reporter))); - } - - let reporter = Arc::new(CompositeReporterPlugin::new(reporters)); - - ConfigPlugins { - config, - ctx, - reporter, - } - } - - fn missing_plugin(&self, path: &Path, phase: &str) -> anyhow::Error { - diagnostic_error!("No {phase} found for path {}", path.display()) - } - - fn missing_pipeline_plugin(&self, path: &Path, phase: &str, pipeline: &str) -> anyhow::Error { - diagnostic_error!( - "No {phase} found for path {} with pipeline {pipeline}", - path.display(), - ) - } -} - -impl Plugins for ConfigPlugins { - #[allow(unused)] - fn bundler(&self) -> Result, anyhow::Error> { - Ok(Box::new(RpcBundlerPlugin::new( - &self.ctx, - &self.config.bundler, - )?)) - } - - #[allow(unused)] - fn compressors(&self, path: &Path) -> Result>, anyhow::Error> { - let mut compressors: Vec> = Vec::new(); - - for compressor in self.config.compressors.get(path).iter() { - compressors.push(Box::new(RpcCompressorPlugin::new(&self.ctx, compressor))); - } - - if compressors.is_empty() { - return Err(self.missing_plugin(path, "compressors")); - } - - Ok(compressors) - } - - fn named_pipelines(&self) -> Vec { - self.config.transformers.named_pipelines() - } - - #[allow(unused)] - fn namers(&self) -> Result>, anyhow::Error> { - let mut namers: Vec> = Vec::new(); - - for namer in self.config.namers.iter() { - namers.push(Box::new(RpcNamerPlugin::new(&self.ctx, namer)?)); - } - - Ok(namers) - } - - #[allow(unused)] - fn optimizers( - &self, - path: &Path, - pipeline: Option, - ) -> Result>, anyhow::Error> { - let mut optimizers: Vec> = Vec::new(); - let named_pattern = pipeline.as_ref().map(|pipeline| NamedPattern { - pipeline, - use_fallback: true, - }); - - for optimizer in self.config.optimizers.get(path, named_pattern).iter() { - optimizers.push(Box::new(RpcOptimizerPlugin::new(&self.ctx, optimizer)?)); - } - - Ok(optimizers) - } - - #[allow(unused)] - fn packager(&self, path: &Path) -> Result, anyhow::Error> { - let packager = self.config.packagers.get(path); - - match packager { - None => Err(self.missing_plugin(path, "packager")), - Some(packager) => Ok(Box::new(RpcPackagerPlugin::new(&self.ctx, packager)?)), - } - } - - fn reporter(&self) -> Arc { - self.reporter.clone() - } - - fn resolvers(&self) -> Result>, anyhow::Error> { - let mut resolvers: Vec> = Vec::new(); - - for resolver in self.config.resolvers.iter() { - if resolver.package_name == "@parcel/resolver-default" { - resolvers.push(Box::new(ParcelResolver::new(&self.ctx))); - continue; - } - - resolvers.push(Box::new(RpcResolverPlugin::new(&self.ctx, resolver)?)); - } - - Ok(resolvers) - } - - #[allow(unused)] - fn runtimes(&self) -> Result>, anyhow::Error> { - let mut runtimes: Vec> = Vec::new(); - - for runtime in self.config.runtimes.iter() { - runtimes.push(Box::new(RpcRuntimePlugin::new(&self.ctx, runtime)?)); - } - - Ok(runtimes) - } - - /// Resolve and load transformer plugins for a given path. - fn transformers( - &self, - path: &Path, - pipeline: Option, - ) -> Result { - let mut transformers: Vec> = Vec::new(); - let named_pattern = pipeline.as_ref().map(|pipeline| NamedPattern { - pipeline, - use_fallback: false, - }); - - let mut hasher = parcel_core::hash::IdentifierHasher::default(); - - for transformer in self.config.transformers.get(path, named_pattern).iter() { - transformer.hash(&mut hasher); - if transformer.package_name == "@parcel/transformer-babel" - || transformer.package_name == "@parcel/transformer-react-refresh-wrap" - { - // Currently JS plugins don't work and it's easier to just skip these. - // We also will probably remove babel from the defaults and support - // react refresh in Rust before releasing native asset graph - continue; - } - - if transformer.package_name == "@parcel/transformer-js" { - transformers.push(Box::new(ParcelJsTransformerPlugin::new(&self.ctx)?)); - continue; - } - - transformers.push(Box::new(RpcTransformerPlugin::new(&self.ctx, transformer)?)); - } - - if transformers.is_empty() { - return match pipeline { - None => Err(self.missing_plugin(path, "transformers")), - Some(pipeline) => Err(self.missing_pipeline_plugin(path, "transformers", &pipeline)), - }; - } - - Ok(TransformerPipeline { - transformers, - hash: hasher.finish(), - }) - } - - #[allow(unused)] - fn validators(&self, _path: &Path) -> Result>, anyhow::Error> { - todo!() - } -} - -#[cfg(test)] -mod tests { - use crate::test_utils::{config_plugins, make_test_plugin_context}; - - use super::*; - - #[test] - fn returns_bundler() { - let bundler = config_plugins(make_test_plugin_context()) - .bundler() - .expect("Not to panic"); - - assert_eq!(format!("{:?}", bundler), "RpcBundlerPlugin") - } - - #[test] - fn returns_compressors() { - let compressors = config_plugins(make_test_plugin_context()) - .compressors(Path::new("a.js")) - .expect("Not to panic"); - - assert_eq!(format!("{:?}", compressors), "[RpcCompressorPlugin]") - } - - #[test] - fn returns_namers() { - let namers = config_plugins(make_test_plugin_context()) - .namers() - .expect("Not to panic"); - - assert_eq!(format!("{:?}", namers), "[RpcNamerPlugin]") - } - - #[test] - fn returns_optimizers() { - let optimizers = config_plugins(make_test_plugin_context()) - .optimizers(Path::new("a.js"), None) - .expect("Not to panic"); - - assert_eq!(format!("{:?}", optimizers), "[RpcOptimizerPlugin]") - } - - #[test] - fn returns_packager() { - let packager = config_plugins(make_test_plugin_context()) - .packager(Path::new("a.js")) - .expect("Not to panic"); - - assert_eq!(format!("{:?}", packager), "RpcPackagerPlugin") - } - - #[test] - fn returns_reporter() { - let reporter = config_plugins(make_test_plugin_context()).reporter(); - - assert_eq!( - format!("{:?}", reporter), - "CompositeReporterPlugin { reporters: [RpcReporterPlugin] }" - ) - } - - #[test] - fn returns_resolvers() { - let resolvers = config_plugins(make_test_plugin_context()) - .resolvers() - .expect("Not to panic"); - - assert_eq!(format!("{:?}", resolvers), "[ParcelResolver]") - } - - #[test] - fn returns_runtimes() { - let runtimes = config_plugins(make_test_plugin_context()) - .runtimes() - .expect("Not to panic"); - - assert_eq!(format!("{:?}", runtimes), "[RpcRuntimePlugin]") - } - - #[test] - fn returns_transformers() { - let pipeline = config_plugins(make_test_plugin_context()) - .transformers(Path::new("a.ts"), None) - .expect("Not to panic"); - - assert_eq!( - format!("{:?}", pipeline), - format!( - "{:?}", - TransformerPipeline { - transformers: vec![Box::new( - ParcelJsTransformerPlugin::new(&make_test_plugin_context()).unwrap() - )], - hash: 1 - } - ) - ); - } -} diff --git a/crates/parcel/src/project_root.rs b/crates/parcel/src/project_root.rs deleted file mode 100644 index 61a3970b544..00000000000 --- a/crates/parcel/src/project_root.rs +++ /dev/null @@ -1,326 +0,0 @@ -use std::path::{Component, Components, Path, PathBuf}; - -use parcel_filesystem::{ - search::{find_ancestor_directory, find_ancestor_file}, - FileSystemRef, -}; - -/// Makes the path absolute without accessing the filesystem -/// -/// This implementation is a modified version of the built-in [absolute](https://doc.rust-lang.org/stable/std/path/fn.absolute.html) function -fn absolute(cwd: &Path, path: &Path) -> PathBuf { - let mut components = path.strip_prefix(".").unwrap_or(path).components(); - let path_os = path.as_os_str().as_encoded_bytes(); - - let mut normalized = if path.is_absolute() { - if path_os.starts_with(b"//") && !path_os.starts_with(b"///") { - components.next(); - PathBuf::from("//") - } else { - PathBuf::new() - } - } else { - PathBuf::from(cwd) - }; - - normalized.extend(components); - - if path_os.ends_with(b"/") { - normalized.push(""); - } - - normalized -} - -/// Finds the common path prefix shared between all input paths -fn common_path(paths: &[PathBuf]) -> Option { - let mut path_components: Vec = paths.iter().map(|path| path.components()).collect(); - let mut common_path: Option = None; - - loop { - // Get the next component for all of our paths - let components = path_components - .iter_mut() - .map(Iterator::next) - .collect::>>(); - - // When the first component is available, check if all other components match as well - if let Some(Some(component)) = components.first() { - if components - .iter() - .all(|c| c.is_some_and(|c| &c == component)) - { - common_path = common_path - .map(|p| p.join(component)) - .or(Some(PathBuf::from(&component))); - continue; - } - } - - // Not all of the components are equal, so we are now done - break; - } - - common_path -} - -pub fn infer_project_root( - fs: FileSystemRef, - entries: Vec, -) -> Result { - let cwd = fs.cwd()?; - - // TODO Handle globs - let entries: Vec = entries - .iter() - .map(|entry| absolute(&cwd, Path::new(&entry))) - .collect(); - - let common_entry_path = common_path(&entries).unwrap_or_else(|| cwd.clone()); - - let root = common_entry_path - .components() - .find(|c| c == &Component::RootDir) - .map(|c| PathBuf::from(&c)) - .unwrap_or_else(|| cwd.clone()); - - let project_root_file = find_ancestor_file( - fs.as_ref(), - &["package-lock.json", "pnpm-lock.yaml", "yarn.lock"], - common_entry_path.clone(), - root.clone(), - ); - - let project_root_dir = project_root_file - .or(find_ancestor_directory( - fs.as_ref(), - &[".git", ".hg"], - common_entry_path.clone(), - root, - )) - .map(|f| f.parent().map(|p| p.to_owned()).unwrap_or(f)); - - Ok(project_root_dir.unwrap_or(cwd)) -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - use parcel_resolver::FileSystem; - - use super::*; - - mod common_path { - use super::*; - - #[test] - fn returns_none_when_there_is_no_common_path() { - assert_eq!( - common_path(&vec![PathBuf::from("dist"), PathBuf::from("src")]), - None, - ); - - assert_eq!( - common_path(&vec![ - PathBuf::from("dist"), - PathBuf::from("packages").join("foo"), - ]), - None, - ); - } - - #[test] - fn returns_the_common_prefix() { - assert_eq!( - common_path(&vec![PathBuf::from("/")]), - Some(PathBuf::from("/")) - ); - - assert_eq!( - common_path(&vec![PathBuf::from("src")]), - Some(PathBuf::from("src")) - ); - - assert_eq!( - common_path(&vec![ - PathBuf::from("packages/foo"), - PathBuf::from("packages/bar"), - PathBuf::from("packages/baz"), - ]), - Some(PathBuf::from("packages")) - ); - - assert_eq!( - common_path(&vec![ - PathBuf::from("packages/foo"), - PathBuf::from("packages/foo/a.js"), - PathBuf::from("packages/foo/bar/b.js"), - ]), - Some(PathBuf::from("packages").join("foo")) - ); - - assert_eq!( - common_path(&vec![ - PathBuf::from("packages/foo/a.js"), - PathBuf::from("packages/bar/b.js"), - PathBuf::from("packages/baz/c.js"), - ]), - Some(PathBuf::from("packages")) - ); - } - } - - mod returns_cwd_when_there_are_no_lockfiles_or_vcs { - use super::*; - - #[test] - fn or_entries() { - let fs = Arc::new(InMemoryFileSystem::default()); - - assert_eq!( - infer_project_root(fs.clone(), Vec::new()).map_err(|e| e.to_string()), - Ok(fs.cwd().unwrap()) - ); - } - - #[test] - fn with_a_single_entry() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = infer_project_root(fs.clone(), vec![String::from("src/a.js")]); - - assert_eq!( - project_root.map_err(|e| e.to_string()), - Ok(fs.cwd().unwrap()) - ); - } - - #[test] - fn with_multiple_entries() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = infer_project_root( - fs.clone(), - vec![String::from("src/a.js"), String::from("src/b.js")], - ); - - assert_eq!( - project_root.map_err(|e| e.to_string()), - Ok(fs.cwd().unwrap()) - ); - } - } - - #[cfg(target_os = "windows")] - fn root() -> PathBuf { - PathBuf::from("C:\\") - } - - #[cfg(not(target_os = "windows"))] - fn root() -> PathBuf { - PathBuf::from("/") - } - - fn cwd() -> PathBuf { - root().join("parcel") - } - - #[test] - fn returns_the_root_lockfile_directory() { - let assert_project_root = |lockfile: &str| { - let entries = vec![String::from("src/a.js")]; - let fs = Arc::new(InMemoryFileSystem::default()); - let root = root(); - - fs.set_current_working_directory(&cwd()); - fs.write_file(&root.join(lockfile), String::from("{}")); - - assert_eq!( - infer_project_root(fs, entries).map_err(|e| e.to_string()), - Ok(root) - ); - }; - - assert_project_root("package-lock.json"); - assert_project_root("pnpm-lock.yaml"); - assert_project_root("yarn.lock"); - } - - mod returns_the_closest_lockfile_directory { - use super::*; - - #[test] - fn given_a_single_entry() { - let assert_project_root = |lockfile| { - let cwd = cwd(); - let entries = vec![String::from("src/a.js")]; - let fs = Arc::new(InMemoryFileSystem::default()); - - fs.set_current_working_directory(&cwd); - fs.write_file(&root().join(lockfile), String::from("{}")); - fs.write_file(&cwd.join(lockfile), String::from("{}")); - - assert_eq!( - infer_project_root(fs, entries).map_err(|e| e.to_string()), - Ok(cwd) - ); - }; - - assert_project_root("package-lock.json"); - assert_project_root("pnpm-lock.yaml"); - assert_project_root("yarn.lock"); - } - - #[test] - fn given_multiple_entries() { - let assert_project_root = |lockfile| { - let cwd = cwd(); - let entries = vec![ - String::from("packages/foo/a.js"), - String::from("packages/bar/b.js"), - String::from("packages/baz/c.js"), - ]; - - let fs = Arc::new(InMemoryFileSystem::default()); - - fs.set_current_working_directory(&cwd); - fs.write_file(&root().join(lockfile), String::from("{}")); - fs.write_file( - &cwd.join("packages").join("foo").join(lockfile), - String::from("{}"), - ); - - assert_eq!( - infer_project_root(fs, entries).map_err(|e| e.to_string()), - Ok(root()) - ); - }; - - assert_project_root("package-lock.json"); - assert_project_root("pnpm-lock.yaml"); - assert_project_root("yarn.lock"); - } - } - - #[test] - fn returns_the_vcs_parent_directory() { - let assert_project_root = |vcs| { - let entries = vec![String::from("src/a.js")]; - let fs = Arc::new(InMemoryFileSystem::default()); - let root = root(); - let vcs = root.join(vcs); - - fs.set_current_working_directory(&cwd()); - fs.create_directory(&vcs) - .expect(format!("Expected {} directory to be created", vcs.display()).as_str()); - - assert_eq!( - infer_project_root(fs, entries).map_err(|e| e.to_string()), - Ok(root) - ); - }; - - assert_project_root(".git"); - assert_project_root(".hg"); - } -} diff --git a/crates/parcel/src/request_tracker/mod.rs b/crates/parcel/src/request_tracker/mod.rs deleted file mode 100644 index 3f3de8a8723..00000000000 --- a/crates/parcel/src/request_tracker/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub use self::request::*; -pub use self::request_graph::*; -#[allow(unused)] -pub use self::request_tracker::*; - -mod request; -mod request_graph; -mod request_tracker; - -#[cfg(test)] -mod test; diff --git a/crates/parcel/src/request_tracker/request.rs b/crates/parcel/src/request_tracker/request.rs deleted file mode 100644 index 6f9dfb41788..00000000000 --- a/crates/parcel/src/request_tracker/request.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fmt::Debug; -use std::hash::Hash; -use std::hash::Hasher; -use std::path::PathBuf; -use std::sync::mpsc::Sender; -use std::sync::Arc; - -use dyn_hash::DynHash; -use parcel_core::config_loader::ConfigLoaderRef; -use parcel_core::types::ParcelOptions; - -use crate::plugins::PluginsRef; -use crate::requests::RequestResult; -use parcel_core::plugin::ReporterEvent; -use parcel_core::types::Invalidation; -use parcel_filesystem::FileSystemRef; - -#[derive(Debug)] -pub struct RunRequestMessage { - pub request: Box, - pub parent_request_id: Option, - pub response_tx: Option>>, -} - -type RunRequestFn = Box; - -/// This is the API for requests to call back onto the `RequestTracker`. -/// -/// We want to avoid exposing internals of the request tracker to the implementations so that we -/// can change this. -pub struct RunRequestContext { - config_loader: ConfigLoaderRef, - file_system: FileSystemRef, - pub options: Arc, - parent_request_id: Option, - plugins: PluginsRef, - pub project_root: PathBuf, - run_request_fn: RunRequestFn, -} - -impl RunRequestContext { - pub(crate) fn new( - config_loader: ConfigLoaderRef, - file_system: FileSystemRef, - options: Arc, - parent_request_id: Option, - plugins: PluginsRef, - project_root: PathBuf, - run_request_fn: RunRequestFn, - ) -> Self { - Self { - config_loader, - file_system, - options, - parent_request_id, - plugins, - project_root, - run_request_fn, - } - } - - /// Report an event - pub fn report(&self, event: ReporterEvent) { - self - .plugins() - .reporter() - .report(&event) - .expect("TODO this should be handled?") - } - - /// Run a child request to the current request - pub fn queue_request( - &mut self, - request: impl Request, - tx: Sender>, - ) -> anyhow::Result<()> { - let request: Box = Box::new(request); - let message = RunRequestMessage { - request, - response_tx: Some(tx), - parent_request_id: self.parent_request_id, - }; - (*self.run_request_fn)(message); - Ok(()) - } - - pub fn file_system(&self) -> &FileSystemRef { - &self.file_system - } - - pub fn plugins(&self) -> &PluginsRef { - &self.plugins - } - - pub fn config(&self) -> &ConfigLoaderRef { - &self.config_loader - } -} - -// We can type this properly -pub type RunRequestError = anyhow::Error; -pub type RequestId = u64; - -pub trait Request: DynHash + Send + Debug + 'static { - fn id(&self) -> RequestId { - let mut hasher = parcel_core::hash::IdentifierHasher::default(); - std::any::type_name::().hash(&mut hasher); - self.dyn_hash(&mut hasher); - hasher.finish() - } - - fn run( - &self, - request_context: RunRequestContext, - ) -> Result; -} - -dyn_hash::hash_trait_object!(Request); - -#[derive(Debug, Clone, PartialEq)] -pub struct ResultAndInvalidations { - pub result: RequestResult, - pub invalidations: Vec, -} diff --git a/crates/parcel/src/request_tracker/request_graph.rs b/crates/parcel/src/request_tracker/request_graph.rs deleted file mode 100644 index 880dc099977..00000000000 --- a/crates/parcel/src/request_tracker/request_graph.rs +++ /dev/null @@ -1,18 +0,0 @@ -use petgraph::stable_graph::StableDiGraph; - -use crate::request_tracker::RunRequestError; - -pub type RequestGraph = StableDiGraph, RequestEdgeType>; - -#[derive(Debug)] -pub enum RequestNode { - Error(RunRequestError), - Root, - Incomplete, - Valid(T), -} - -#[derive(Debug)] -pub enum RequestEdgeType { - SubRequest, -} diff --git a/crates/parcel/src/request_tracker/request_tracker.rs b/crates/parcel/src/request_tracker/request_tracker.rs deleted file mode 100644 index 98a74a8b44a..00000000000 --- a/crates/parcel/src/request_tracker/request_tracker.rs +++ /dev/null @@ -1,315 +0,0 @@ -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::mpsc::Sender; -use std::sync::Arc; - -use anyhow::anyhow; -use parcel_core::config_loader::ConfigLoaderRef; -use parcel_core::diagnostic_error; -use parcel_core::types::ParcelOptions; -use parcel_filesystem::FileSystemRef; -use petgraph::graph::NodeIndex; -use petgraph::stable_graph::StableDiGraph; - -use crate::plugins::PluginsRef; -use crate::requests::RequestResult; - -use super::Request; -use super::RequestEdgeType; -use super::RequestGraph; -use super::RequestId; -use super::RequestNode; -use super::ResultAndInvalidations; -use super::RunRequestError; -use super::{RunRequestContext, RunRequestMessage}; - -/// [`RequestTracker`] runs parcel work items and constructs a graph of their dependencies. -/// -/// Whenever a [`Request`] implementation needs to get the result of another piece of work, it'll -/// make a call into [`RequestTracker`] through its [`RunRequestContext`] abstraction. The request -/// tracker will verify if the piece of work has been completed and return its result. If the work -/// has not been seen yet, it'll be scheduled for execution. -/// -/// By asking for the result of a piece of work (through [`RunRequestContext::queue_request`]) a -/// request is creating an edge between itself and that sub-request. -/// -/// This will be used to trigger cache invalidations. -pub struct RequestTracker { - config_loader: ConfigLoaderRef, - file_system: FileSystemRef, - graph: RequestGraph, - options: Arc, - plugins: PluginsRef, - project_root: PathBuf, - request_index: HashMap, -} - -impl RequestTracker { - #[allow(unused)] - pub fn new( - config_loader: ConfigLoaderRef, - file_system: FileSystemRef, - options: Arc, - plugins: PluginsRef, - project_root: PathBuf, - ) -> Self { - let mut graph = StableDiGraph::, RequestEdgeType>::new(); - - graph.add_node(RequestNode::Root); - RequestTracker { - config_loader, - file_system, - graph, - plugins, - project_root, - request_index: HashMap::new(), - options, - } - } - - /// Run a request that has no parent. Return the result. - /// - /// ## Multi-threading - /// Sub-requests may be queued from this initial `request` using - /// [`RunRequestContext::queue_request`]. - /// All sub-requests will run on separate tasks on a thread-pool. - /// - /// A request may use the channel passed into [`RunRequestContext::queue_request`] to wait for - /// results from sub-requests. - /// - /// Because threads will be blocked by waiting on sub-requests, the system may stall if the thread - /// pool runs out of threads. For the same reason, the number of threads must always be greater - /// than 1. For this reason the minimum number of threads our thread-pool uses is 4. - /// - /// There are multiple ways we can fix this in our implementation: - /// * Use async, so we get cooperative multi-threading and don't need to worry about this - /// * Whenever we block a thread, block using recv_timeout and then use [`rayon::yield_now`] so - /// other tasks get a chance to tick on our thread-pool. This is a very poor implementation of - /// the cooperative threading behaviours async will grant us. - /// * Don't use rayon for multi-threading here and use a custom thread-pool implementation which - /// ensures we always have more threads than concurrently running requests - /// * Run requests that need to spawn multithreaded sub-requests on the main-thread - /// - That is, introduce a new `MainThreadRequest` trait, which is able to enqueue requests, - /// these will run on the main-thread, therefore it'll be simpler to implement queueing - /// without stalls and locks/channels - /// - For non-main-thread requests, do not allow enqueueing of sub-requests - #[allow(unused)] - pub fn run_request(&mut self, request: impl Request) -> anyhow::Result { - let thread_pool = rayon::ThreadPoolBuilder::new() - .thread_name(|count| format!("RequestTracker-{}", count)) - .num_threads(num_cpus::get() + 4) - .panic_handler(|failure| { - tracing::error!("Lost thread from thread-pool. This is a bug in parcel. Builds may stall."); - std::process::exit(1); - }) - .build()?; - thread_pool.in_place_scope(|scope| { - let request_id = request.id(); - let (tx, rx) = std::sync::mpsc::channel(); - let tx2 = tx.clone(); - let _ = tx.send(RequestQueueMessage::RunRequest { - tx: tx2, - message: RunRequestMessage { - request: Box::new(request), - parent_request_id: None, - response_tx: None, - }, - }); - drop(tx); - - while let Ok(request_queue_message) = rx.recv() { - tracing::debug!("Received message {:?}", request_queue_message); - match request_queue_message { - RequestQueueMessage::RunRequest { - message: - RunRequestMessage { - request, - parent_request_id, - response_tx, - }, - tx, - } => { - let request_id = request.id(); - if self.prepare_request(request_id)? { - let context = RunRequestContext::new( - self.config_loader.clone(), - self.file_system.clone(), - self.options.clone(), - Some(request_id), - self.plugins.clone(), - self.project_root.clone(), - // sub-request run - Box::new({ - let tx = tx.clone(); - move |message| { - let tx2 = tx.clone(); - tx.send(RequestQueueMessage::RunRequest { message, tx: tx2 }) - .unwrap(); - } - }), - ); - - scope.spawn({ - let tx = tx.clone(); - move |_scope| { - let result = request.run(context); - let _ = tx.send(RequestQueueMessage::RequestResult { - request_id, - parent_request_id, - result, - response_tx, - }); - } - }) - } else { - // Cached request - if let Some(response_tx) = response_tx { - let result = self.get_request(parent_request_id, request_id); - let _ = response_tx.send(result.map(|r| (r, request_id))); - } - }; - } - RequestQueueMessage::RequestResult { - request_id, - parent_request_id, - result, - response_tx, - } => { - self.store_request(request_id, &result)?; - self.link_request_to_parent(request_id, parent_request_id)?; - - if let Some(response_tx) = response_tx { - let _ = response_tx.send(result.map(|result| (result.result, request_id))); - } - } - } - } - - self.get_request(None, request_id) - }) - } - - /// Before a request is run, a 'pending' [`RequestNode::Incomplete`] entry is added to the graph. - #[allow(unused)] - fn prepare_request(&mut self, request_id: u64) -> anyhow::Result { - let node_index = self - .request_index - .entry(request_id) - .or_insert_with(|| self.graph.add_node(RequestNode::Incomplete)); - - let request_node = self - .graph - .node_weight_mut(*node_index) - .ok_or_else(|| diagnostic_error!("Failed to find request node"))?; - - // Don't run if already run - if let RequestNode::::Valid(_) = request_node { - return Ok(false); - } - - *request_node = RequestNode::Incomplete; - Ok(true) - } - - /// Once a request finishes, its result is stored under its [`RequestNode`] entry on the graph - #[allow(unused)] - fn store_request( - &mut self, - request_id: u64, - result: &Result, - ) -> anyhow::Result<()> { - let node_index = self - .request_index - .get(&request_id) - .ok_or_else(|| diagnostic_error!("Failed to find request"))?; - - let request_node = self - .graph - .node_weight_mut(*node_index) - .ok_or_else(|| diagnostic_error!("Failed to find request"))?; - - if let RequestNode::::Valid(_) = request_node { - return Ok(()); - } - - *request_node = match result { - Ok(result) => RequestNode::Valid(result.result.clone()), - Err(error) => RequestNode::Error(anyhow!(error.to_string())), - }; - - Ok(()) - } - - /// Get a request result and call [`RequestTracker::link_request_to_parent`] to create a - /// dependency link between the source request and this sub-request. - #[allow(unused)] - fn get_request( - &mut self, - parent_request_hash: Option, - request_id: u64, - ) -> anyhow::Result { - self.link_request_to_parent(request_id, parent_request_hash)?; - - let Some(node_index) = self.request_index.get(&request_id) else { - return Err(diagnostic_error!("Impossible error")); - }; - let Some(request_node) = self.graph.node_weight(*node_index) else { - return Err(diagnostic_error!("Impossible")); - }; - - match request_node { - RequestNode::Root => Err(diagnostic_error!("Impossible")), - RequestNode::Incomplete => Err(diagnostic_error!("Impossible")), - RequestNode::Error(error) => Err(anyhow!(error.to_string())), - RequestNode::Valid(value) => Ok(value.clone()), - } - } - - /// Create an edge between a parent request and the target request. - #[allow(unused)] - fn link_request_to_parent( - &mut self, - request_id: u64, - parent_request_hash: Option, - ) -> anyhow::Result<()> { - let Some(node_index) = self.request_index.get(&request_id) else { - return Err(diagnostic_error!("Impossible error")); - }; - - if let Some(parent_request_id) = parent_request_hash { - let parent_node_index = self - .request_index - .get(&parent_request_id) - .ok_or_else(|| diagnostic_error!("Failed to find requests"))?; - - self - .graph - .add_edge(*parent_node_index, *node_index, RequestEdgeType::SubRequest); - } else { - self - .graph - .add_edge(NodeIndex::new(0), *node_index, RequestEdgeType::SubRequest); - } - Ok(()) - } -} - -/// Internally, [`RequestTracker`] ticks a queue of work related to each 'entry request' ran. -/// -/// This enum represents messages that can be sent to the main-thread that is ticking the work for -/// an entry from worker threads that are processing individual requests. -/// -/// See [`RequestTracker::run_request`]. -#[derive(Debug)] -enum RequestQueueMessage { - RunRequest { - tx: Sender, - message: RunRequestMessage, - }, - RequestResult { - request_id: RequestId, - parent_request_id: Option, - result: Result, - response_tx: Option>>, - }, -} diff --git a/crates/parcel/src/request_tracker/test.rs b/crates/parcel/src/request_tracker/test.rs deleted file mode 100644 index 73d48da4d72..00000000000 --- a/crates/parcel/src/request_tracker/test.rs +++ /dev/null @@ -1,233 +0,0 @@ -use core::panic; -use std::collections::HashSet; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; -use std::sync::mpsc::channel; -use std::sync::Arc; -use std::time::Duration; - -use crate::requests::RequestResult; -use crate::test_utils::request_tracker; - -use super::*; - -#[test] -fn should_run_request() { - let mut rt = request_tracker(Default::default()); - - let request_c = TestRequest::new("C", &[]); - let request_b = TestRequest::new("B", &[request_c.clone()]); - let request_a = TestRequest::new("A", &[request_b.clone()]); - - let result = run_request(&mut rt, &request_a); - - assert_eq!(result[0], "A"); - assert_eq!(result[1], "B"); - assert_eq!(result[2], "C"); -} - -#[test] -fn should_reuse_previously_run_request() { - let mut rt = request_tracker(Default::default()); - - let request_c = TestRequest::new("C", &[]); - let request_b = TestRequest::new("B", &[request_c.clone()]); - let request_a = TestRequest::new("A", &[request_b.clone()]); - - let result = run_request(&mut rt, &request_a); - - assert_eq!(result[0], "A"); - assert_eq!(result[1], "B"); - assert_eq!(result[2], "C"); - - let result = run_request(&mut rt, &request_a); - - assert_eq!(result[0], "A"); - assert_eq!(result[1], "B"); - assert_eq!(result[2], "C"); -} - -#[test] -fn should_run_request_once() { - let mut rt = request_tracker(Default::default()); - - let request_a = TestRequest::new("A", &[]); - - let result = run_sub_request(&mut rt, &request_a); - - assert_eq!(result, "A"); - assert_eq!(request_a.run_count(), 1); - - let result = run_sub_request(&mut rt, &request_a); - assert_eq!(result, "A"); - assert_eq!(request_a.run_count(), 1); -} - -#[test] -fn should_run_request_once_2() { - let mut rt = request_tracker(Default::default()); - - let request_b = TestRequest::new("B", &[]); - let request_a = TestRequest::new("A", &[request_b.clone()]); - - let result = run_request(&mut rt, &request_a); - - assert_eq!(result[0], "A"); - assert_eq!(result[1], "B"); - assert_eq!(request_a.run_count(), 1); - assert_eq!(request_b.run_count(), 1); - - let result = run_request(&mut rt, &request_a); - assert_eq!(result[0], "A"); - assert_eq!(result[1], "B"); - assert_eq!(request_a.run_count(), 1); - assert_eq!(request_b.run_count(), 1); -} - -fn run_request(request_tracker: &mut RequestTracker, request: &TestRequest) -> Vec { - let RequestResult::TestMain(result) = request_tracker.run_request(request.clone()).unwrap() - else { - panic!("Unexpected result"); - }; - result -} - -fn run_sub_request(request_tracker: &mut RequestTracker, request: &TestRequest) -> String { - let RequestResult::TestSub(result) = request_tracker.run_request(request.clone()).unwrap() else { - panic!("Unexpected result"); - }; - result -} - -/// This is a universal "Request" that can be instructed -/// to run subrequests via the constructor -#[derive(Clone, Default, Debug)] -pub struct TestRequest { - pub runs: Arc, - pub name: String, - pub subrequests: Vec, -} - -impl TestRequest { - pub fn new>(name: T, subrequests: &[TestRequest]) -> Self { - Self { - runs: Default::default(), - name: name.as_ref().to_string(), - subrequests: subrequests.to_owned(), - } - } - - pub fn run_count(&self) -> usize { - self.runs.load(Ordering::Relaxed) - } -} - -impl std::hash::Hash for TestRequest { - fn hash(&self, state: &mut H) { - self.name.hash(state); - } -} - -impl Request for TestRequest { - fn run( - &self, - mut request_context: RunRequestContext, - ) -> Result { - self.runs.fetch_add(1, Ordering::Relaxed); - - let name = self.name.clone(); - - let mut subrequests = self.subrequests.clone(); - - if subrequests.is_empty() { - return Ok(ResultAndInvalidations { - result: RequestResult::TestSub(name), - invalidations: vec![], - }); - } - - let (tx, rx) = channel(); - - while let Some(subrequest) = subrequests.pop() { - let req = subrequest.clone(); - let _ = request_context.queue_request(req, tx.clone()); - } - drop(tx); - - let mut results = vec![name]; - while let Ok(response) = rx.recv_timeout(Duration::from_secs(2)) { - match response { - Ok((RequestResult::TestSub(result), _id)) => results.push(result), - Ok((RequestResult::TestMain(sub_results), _id)) => results.extend(sub_results), - a => todo!("{:?}", a), - } - } - - Ok(ResultAndInvalidations { - result: RequestResult::TestMain(results), - invalidations: vec![], - }) - } -} - -#[derive(Debug, Hash)] -struct TestChildRequest { - count: u32, -} -impl Request for TestChildRequest { - fn run( - &self, - _request_context: RunRequestContext, - ) -> Result { - Ok(ResultAndInvalidations { - result: RequestResult::TestSub(self.count.to_string()), - invalidations: vec![], - }) - } -} -#[derive(Debug, Hash)] -struct TestRequest2 { - sub_requests: u32, -} -impl Request for TestRequest2 { - fn run( - &self, - mut request_context: RunRequestContext, - ) -> Result { - let (tx, rx) = channel(); - - for count in 0..self.sub_requests { - let _ = request_context.queue_request(TestChildRequest { count }, tx.clone()); - } - drop(tx); - - let mut responses = Vec::new(); - while let Ok(response) = rx.recv_timeout(Duration::from_secs(2)) { - match response { - Ok((RequestResult::TestSub(result), _idd)) => responses.push(result), - _ => todo!("unimplemented"), - } - } - - Ok(ResultAndInvalidations { - result: RequestResult::TestMain(responses), - invalidations: vec![], - }) - } -} - -#[test] -fn test_queued_subrequests() { - let sub_requests = 20; - let result = request_tracker(Default::default()).run_request(TestRequest2 { sub_requests }); - - match result { - Ok(RequestResult::TestMain(responses)) => { - let expected: HashSet = (0..sub_requests).map(|v| v.to_string()).collect(); - assert_eq!(HashSet::from_iter(responses.iter().cloned()), expected); - } - _ => { - panic!("Request should pass"); - } - } -} diff --git a/crates/parcel/src/requests.rs b/crates/parcel/src/requests.rs deleted file mode 100644 index e7e548200a8..00000000000 --- a/crates/parcel/src/requests.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub use asset_graph_request::*; -use asset_request::AssetRequestOutput; -use entry_request::EntryRequestOutput; -use path_request::PathRequestOutput; -use target_request::TargetRequestOutput; - -mod asset_graph_request; -mod asset_request; -mod entry_request; -mod path_request; -mod target_request; - -/// Union of all request outputs -#[derive(Clone, Debug, PartialEq)] -pub enum RequestResult { - AssetGraph(AssetGraphRequestOutput), - Asset(AssetRequestOutput), - Entry(EntryRequestOutput), - Path(PathRequestOutput), - Target(TargetRequestOutput), - // The following are test request types only used in the test build - #[cfg(test)] - TestSub(String), - #[cfg(test)] - TestMain(Vec), -} diff --git a/crates/parcel/src/requests/asset_graph_request.rs b/crates/parcel/src/requests/asset_graph_request.rs deleted file mode 100644 index 0d8afae02da..00000000000 --- a/crates/parcel/src/requests/asset_graph_request.rs +++ /dev/null @@ -1,543 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Arc; - -use anyhow::anyhow; -use pathdiff::diff_paths; -use petgraph::graph::NodeIndex; - -use parcel_core::asset_graph::{AssetGraph, DependencyNode, DependencyState}; -use parcel_core::types::Dependency; - -use crate::request_tracker::{Request, ResultAndInvalidations, RunRequestContext, RunRequestError}; - -use super::asset_request::{AssetRequest, AssetRequestOutput}; -use super::entry_request::{EntryRequest, EntryRequestOutput}; -use super::path_request::{PathRequest, PathRequestOutput}; -use super::target_request::{TargetRequest, TargetRequestOutput}; -use super::RequestResult; - -type ResultSender = Sender>; -type ResultReceiver = Receiver>; - -/// The AssetGraphRequest is in charge of building the AssetGraphRequest -/// In doing so, it kicks of the EntryRequest, TargetRequest, PathRequest and AssetRequests. -#[derive(Debug, Hash)] -pub struct AssetGraphRequest {} - -#[derive(Clone, Debug, PartialEq)] -pub struct AssetGraphRequestOutput { - pub graph: AssetGraph, -} - -impl Request for AssetGraphRequest { - fn run( - &self, - request_context: RunRequestContext, - ) -> Result { - let builder = AssetGraphBuilder::new(request_context); - - builder.build() - } -} - -struct AssetGraphBuilder { - request_id_to_dep_node_index: HashMap, - graph: AssetGraph, - visited: HashSet, - work_count: u32, - request_context: RunRequestContext, - sender: ResultSender, - receiver: ResultReceiver, - asset_request_to_asset: HashMap, - waiting_asset_requests: HashMap>, -} - -impl AssetGraphBuilder { - fn new(request_context: RunRequestContext) -> Self { - let (sender, receiver) = channel(); - - AssetGraphBuilder { - request_id_to_dep_node_index: HashMap::new(), - graph: AssetGraph::new(), - visited: HashSet::new(), - work_count: 0, - request_context, - sender, - receiver, - asset_request_to_asset: HashMap::new(), - waiting_asset_requests: HashMap::new(), - } - } - - fn build(mut self) -> Result { - for entry in self.request_context.options.clone().entries.iter() { - self.work_count += 1; - let _ = self.request_context.queue_request( - EntryRequest { - entry: entry.clone(), - }, - self.sender.clone(), - ); - } - - loop { - // TODO: Should the work count be tracked on the request_context as part of - // the queue_request API? - if self.work_count == 0 { - break; - } - - let Ok(result) = self.receiver.recv() else { - break; - }; - - self.work_count -= 1; - - match result { - Ok((RequestResult::Entry(result), _request_id)) => { - tracing::debug!("Handling EntryRequestOutput"); - self.handle_entry_result(result); - } - Ok((RequestResult::Target(result), _request_id)) => { - tracing::debug!("Handling TargetRequestOutput"); - self.handle_target_request_result(result); - } - Ok((RequestResult::Asset(result), request_id)) => { - tracing::debug!( - "Handling AssetRequestOutput: {}", - result.asset.file_path.display() - ); - self.handle_asset_result(result, request_id); - } - Ok((RequestResult::Path(result), request_id)) => { - tracing::debug!("Handling PathRequestOutput"); - self.handle_path_result(result, request_id); - } - Err(err) => return Err(err), - // This branch should never occur - Ok((result, request_id)) => { - return Err(anyhow!( - "Unexpected request result in AssetGraphRequest ({}): {:?}", - request_id, - result - )) - } - } - } - - Ok(ResultAndInvalidations { - result: RequestResult::AssetGraph(AssetGraphRequestOutput { graph: self.graph }), - invalidations: vec![], - }) - } - - fn handle_path_result(&mut self, result: PathRequestOutput, request_id: u64) { - let node = *self - .request_id_to_dep_node_index - .get(&request_id) - .expect("Missing node index for request id {request_id}"); - let dep_index = self.graph.dependency_index(node).unwrap(); - let DependencyNode { - dependency, - requested_symbols, - state, - } = &mut self.graph.dependencies[dep_index]; - let asset_request = match result { - PathRequestOutput::Resolved { - path, - code, - pipeline, - side_effects, - query, - can_defer, - } => { - if !side_effects && can_defer && requested_symbols.is_empty() && dependency.has_symbols { - *state = DependencyState::Deferred; - return; - } - - *state = DependencyState::Resolved; - AssetRequest { - file_path: path, - code: code.clone(), - pipeline: pipeline.clone(), - side_effects, - env: dependency.env.clone(), - query, - } - } - PathRequestOutput::Excluded => { - *state = DependencyState::Excluded; - return; - } - }; - let id = asset_request.id(); - - if self.visited.insert(id) { - self.request_id_to_dep_node_index.insert(id, node); - self.work_count += 1; - let _ = self - .request_context - .queue_request(asset_request, self.sender.clone()); - } else if let Some(asset_node_index) = self.asset_request_to_asset.get(&id) { - // We have already completed this AssetRequest so we can connect the - // Dependency to the Asset immediately - self.graph.add_edge(&node, asset_node_index); - self.graph.propagate_requested_symbols( - *asset_node_index, - node, - &mut |dependency_node_index: NodeIndex, dependency: Arc| { - Self::on_undeferred( - &mut self.request_id_to_dep_node_index, - &mut self.work_count, - &mut self.request_context, - &self.sender, - dependency_node_index, - dependency, - ); - }, - ); - } else { - // The AssetRequest has already been kicked off but is yet to - // complete. Register this Dependency to be connected once it - // completes - self - .waiting_asset_requests - .entry(id) - .and_modify(|nodes| { - nodes.insert(node); - }) - .or_insert_with(|| HashSet::from([node])); - } - } - - fn handle_entry_result(&mut self, result: EntryRequestOutput) { - let EntryRequestOutput { entries } = result; - for entry in entries { - let target_request = TargetRequest { - default_target_options: self.request_context.options.default_target_options.clone(), - entry, - env: self.request_context.options.env.clone(), - mode: self.request_context.options.mode.clone(), - }; - - self.work_count += 1; - let _ = self - .request_context - .queue_request(target_request, self.sender.clone()); - } - } - - fn handle_asset_result(&mut self, result: AssetRequestOutput, request_id: u64) { - let AssetRequestOutput { - asset, - dependencies, - } = result; - let incoming_dep_node_index = *self - .request_id_to_dep_node_index - .get(&request_id) - .expect("Missing node index for request id {request_id}"); - - // Connect the incoming DependencyNode to the new AssetNode - let asset_node_index = self.graph.add_asset(incoming_dep_node_index, asset.clone()); - - self - .asset_request_to_asset - .insert(request_id, asset_node_index); - - // Connect dependencies of the Asset - for dependency in &dependencies { - let _ = self - .graph - .add_dependency(asset_node_index, dependency.clone()); - } - - self.graph.propagate_requested_symbols( - asset_node_index, - incoming_dep_node_index, - &mut |dependency_node_index: NodeIndex, dependency: Arc| { - Self::on_undeferred( - &mut self.request_id_to_dep_node_index, - &mut self.work_count, - &mut self.request_context, - &self.sender, - dependency_node_index, - dependency, - ); - }, - ); - - // Connect any previously discovered Dependencies that were waiting - // for this AssetNode to be created - if let Some(waiting) = self.waiting_asset_requests.remove(&request_id) { - for dep in waiting { - self.graph.add_edge(&dep, &asset_node_index); - self.graph.propagate_requested_symbols( - asset_node_index, - dep, - &mut |dependency_node_index: NodeIndex, dependency: Arc| { - Self::on_undeferred( - &mut self.request_id_to_dep_node_index, - &mut self.work_count, - &mut self.request_context, - &self.sender, - dependency_node_index, - dependency, - ); - }, - ); - } - } - } - - fn handle_target_request_result(&mut self, result: TargetRequestOutput) { - let TargetRequestOutput { entry, targets } = result; - for target in targets { - let entry = - diff_paths(&entry, &self.request_context.project_root).unwrap_or_else(|| entry.clone()); - - let dependency = Dependency::entry(entry.to_str().unwrap().to_string(), target); - - let dep_node = self.graph.add_entry_dependency(dependency.clone()); - - let request = PathRequest { - dependency: Arc::new(dependency), - }; - self - .request_id_to_dep_node_index - .insert(request.id(), dep_node); - self.work_count += 1; - let _ = self - .request_context - .queue_request(request, self.sender.clone()); - } - } - - /// When we find dependencies, we will only trigger resolution and parsing for dependencies - /// that have used symbols. - /// - /// Once they do have symbols in use, this callback will re-trigger resolution/transformation - /// for those files. - fn on_undeferred( - request_id_to_dep_node_index: &mut HashMap, - work_count: &mut u32, - request_context: &mut RunRequestContext, - sender: &ResultSender, - dependency_node_index: NodeIndex, - dependency: Arc, - ) { - let request = PathRequest { - dependency: dependency.clone(), - }; - - request_id_to_dep_node_index.insert(request.id(), dependency_node_index); - tracing::debug!( - "queueing a path request from on_undeferred, {}", - dependency.specifier - ); - *work_count += 1; - let _ = request_context.queue_request(request, sender.clone()); - } -} - -#[cfg(test)] -mod test { - use std::path::{Path, PathBuf}; - use std::sync::Arc; - - use tracing::Level; - - use parcel_core::types::{Code, ParcelOptions}; - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - use parcel_filesystem::FileSystem; - - use crate::requests::{AssetGraphRequest, RequestResult}; - use crate::test_utils::{request_tracker, RequestTrackerTestOptions}; - - #[test] - fn test_asset_graph_request_with_no_entries() { - let options = RequestTrackerTestOptions::default(); - let mut request_tracker = request_tracker(options); - - let asset_graph_request = AssetGraphRequest {}; - let RequestResult::AssetGraph(asset_graph_request_result) = - request_tracker.run_request(asset_graph_request).unwrap() - else { - assert!(false, "Got invalid result"); - return; - }; - - assert_eq!(asset_graph_request_result.graph.assets.len(), 0); - assert_eq!(asset_graph_request_result.graph.dependencies.len(), 0); - } - - #[test] - fn test_asset_graph_request_with_a_single_entry_with_no_dependencies() { - let _ = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(Level::DEBUG) - .try_init(); - - let mut options = RequestTrackerTestOptions::default(); - let fs = InMemoryFileSystem::default(); - #[cfg(not(target_os = "windows"))] - let temporary_dir = PathBuf::from("/parcel_tests"); - #[cfg(target_os = "windows")] - let temporary_dir = PathBuf::from("c:/windows/parcel_tests"); - assert!(temporary_dir.is_absolute()); - fs.create_directory(&temporary_dir).unwrap(); - fs.set_current_working_directory(&temporary_dir); // <- resolver is broken without this - options - .parcel_options - .entries - .push(temporary_dir.join("entry.js").to_str().unwrap().to_string()); - options.project_root = temporary_dir.clone(); - options.search_path = temporary_dir.clone(); - fs.write_file( - &temporary_dir.join("entry.js"), - String::from( - r#" -console.log('hello world'); - "#, - ), - ); - options.fs = Arc::new(fs); - - let mut request_tracker = request_tracker(options); - - let asset_graph_request = AssetGraphRequest {}; - let RequestResult::AssetGraph(asset_graph_request_result) = request_tracker - .run_request(asset_graph_request) - .expect("Failed to run asset graph request") - else { - assert!(false, "Got invalid result"); - return; - }; - - assert_eq!(asset_graph_request_result.graph.assets.len(), 1); - assert_eq!(asset_graph_request_result.graph.dependencies.len(), 1); - assert_eq!( - asset_graph_request_result - .graph - .assets - .get(0) - .unwrap() - .asset - .file_path, - temporary_dir.join("entry.js") - ); - assert_eq!( - asset_graph_request_result - .graph - .assets - .get(0) - .unwrap() - .asset - .code, - Arc::new(Code::from( - String::from( - r#" -console.log('hello world'); - "# - ) - .trim_start() - .trim_end_matches(|p| p == ' ') - .to_string() - )) - ); - } - - #[test] - fn test_asset_graph_request_with_a_couple_of_entries() { - #[cfg(not(target_os = "windows"))] - let temporary_dir = PathBuf::from("/parcel_tests"); - #[cfg(target_os = "windows")] - let temporary_dir = PathBuf::from("C:\\windows\\parcel_tests"); - - let core_path = temporary_dir.join("parcel_core"); - let fs = InMemoryFileSystem::default(); - - fs.create_directory(&temporary_dir).unwrap(); - fs.set_current_working_directory(&temporary_dir); - - fs.write_file( - &temporary_dir.join("entry.js"), - String::from( - r#" - import {x} from './a'; - import {y} from './b'; - console.log(x + y); - "#, - ), - ); - - fs.write_file( - &temporary_dir.join("a.js"), - String::from( - r#" - export const x = 15; - "#, - ), - ); - - fs.write_file( - &temporary_dir.join("b.js"), - String::from( - r#" - export const y = 27; - "#, - ), - ); - - setup_core_modules(&fs, &core_path); - - let mut request_tracker = request_tracker(RequestTrackerTestOptions { - fs: Arc::new(fs), - parcel_options: ParcelOptions { - core_path, - entries: vec![temporary_dir.join("entry.js").to_str().unwrap().to_string()], - ..ParcelOptions::default() - }, - project_root: temporary_dir.clone(), - search_path: temporary_dir.clone(), - ..RequestTrackerTestOptions::default() - }); - - let asset_graph_request = AssetGraphRequest {}; - let RequestResult::AssetGraph(asset_graph_request_result) = request_tracker - .run_request(asset_graph_request) - .expect("Failed to run asset graph request") - else { - assert!(false, "Got invalid result"); - return; - }; - - // Entry, 2 assets + helpers file - assert_eq!(asset_graph_request_result.graph.assets.len(), 4); - // Entry, entry to assets (2), assets to helpers (2) - assert_eq!(asset_graph_request_result.graph.dependencies.len(), 5); - - assert_eq!( - asset_graph_request_result - .graph - .assets - .get(0) - .unwrap() - .asset - .file_path, - temporary_dir.join("entry.js") - ); - } - - fn setup_core_modules(fs: &InMemoryFileSystem, core_path: &Path) { - let transformer_path = core_path - .join("node_modules") - .join("@parcel/transformer-js"); - - fs.write_file(&transformer_path.join("package.json"), String::from("{}")); - fs.write_file( - &transformer_path.join("src").join("esmodule-helpers.js"), - String::from("/* helpers */"), - ); - } -} diff --git a/crates/parcel/src/requests/asset_request.rs b/crates/parcel/src/requests/asset_request.rs deleted file mode 100644 index 69f2c01a4ca..00000000000 --- a/crates/parcel/src/requests/asset_request.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::hash::Hash; -use std::path::PathBuf; -use std::sync::Arc; - -use parcel_core::diagnostic_error; -use parcel_core::plugin::AssetBuildEvent; -use parcel_core::plugin::BuildProgressEvent; -use parcel_core::plugin::InitialAsset; -use parcel_core::plugin::ReporterEvent; -use parcel_core::plugin::TransformResult; -use parcel_core::plugin::TransformationInput; -use parcel_core::types::Asset; -use parcel_core::types::AssetStats; -use parcel_core::types::Dependency; -use parcel_core::types::Environment; -use parcel_core::types::FileType; - -use crate::plugins::PluginsRef; -use crate::plugins::TransformerPipeline; -use crate::request_tracker::{Request, ResultAndInvalidations, RunRequestContext, RunRequestError}; - -use super::RequestResult; - -/// The AssetRequest runs transformer plugins on discovered Assets. -/// - Decides which transformer pipeline to run from the input Asset type -/// - Runs the pipeline in series, switching pipeline if the Asset type changes -/// - Stores the final Asset source code in the cache, for access in packaging -/// - Finally, returns the complete Asset and it's discovered Dependencies -#[derive(Clone, Debug, Hash, PartialEq)] -pub struct AssetRequest { - pub env: Arc, - pub file_path: PathBuf, - pub code: Option, - pub pipeline: Option, - pub side_effects: bool, - pub query: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct AssetRequestOutput { - pub asset: Asset, - pub dependencies: Vec, -} - -impl Request for AssetRequest { - fn run( - &self, - request_context: RunRequestContext, - ) -> Result { - request_context.report(ReporterEvent::BuildProgress(BuildProgressEvent::Building( - AssetBuildEvent { - // TODO: Should we try avoid a clone here? - file_path: self.file_path.clone(), - }, - ))); - - let pipeline = request_context - .plugins() - .transformers(&self.file_path, self.pipeline.clone())?; - let asset_type = FileType::from_extension( - self - .file_path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or(""), - ); - - let result = run_pipeline( - pipeline, - TransformationInput::InitialAsset(InitialAsset { - // TODO: Are these clones necessary? - file_path: self.file_path.clone(), - code: self.code.clone(), - env: self.env.clone(), - side_effects: self.side_effects, - }), - asset_type, - request_context.plugins().clone(), - )?; - - Ok(ResultAndInvalidations { - result: RequestResult::Asset(AssetRequestOutput { - asset: Asset { - stats: AssetStats { - size: result.asset.code.size(), - time: 0, - }, - ..result.asset - }, - dependencies: result.dependencies, - }), - // TODO: Support invalidations - invalidations: vec![], - }) - } -} - -fn run_pipeline( - mut pipeline: TransformerPipeline, - input: TransformationInput, - file_type: FileType, - plugins: PluginsRef, -) -> anyhow::Result { - let mut dependencies = vec![]; - let mut invalidations = vec![]; - - let mut transform_input = input; - - let pipeline_hash = pipeline.hash(); - for transformer in &mut pipeline.transformers { - let transform_result = transformer.transform(transform_input)?; - let is_different_asset_type = transform_result.asset.file_type != file_type; - - transform_input = TransformationInput::Asset(transform_result.asset); - - // If the Asset has changed type then we may need to trigger a different pipeline - if is_different_asset_type { - let next_pipeline = plugins.transformers(transform_input.file_path(), None)?; - - if next_pipeline.hash() != pipeline_hash { - return run_pipeline(next_pipeline, transform_input, file_type, plugins); - }; - } - - dependencies.extend(transform_result.dependencies); - invalidations.extend(transform_result.invalidate_on_file_change); - } - - if let TransformationInput::Asset(asset) = transform_input { - Ok(TransformResult { - asset, - dependencies, - invalidate_on_file_change: invalidations, - }) - } else { - Err(diagnostic_error!("No transformations for Asset")) - } -} diff --git a/crates/parcel/src/requests/entry_request.rs b/crates/parcel/src/requests/entry_request.rs deleted file mode 100644 index 3724e7d0b97..00000000000 --- a/crates/parcel/src/requests/entry_request.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::hash::Hash; -use std::path::PathBuf; - -use anyhow::anyhow; - -use super::RequestResult; - -use crate::request_tracker::{Request, ResultAndInvalidations, RunRequestContext, RunRequestError}; - -/// A resolved entry file for the build -#[derive(Clone, Debug, Default, Hash, PartialEq)] -pub struct Entry { - pub file_path: PathBuf, - pub target: Option, -} - -/// The EntryRequest resolves an entry path or glob to the actual file location -#[derive(Debug, Hash)] -pub struct EntryRequest { - pub entry: String, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct EntryRequestOutput { - pub entries: Vec, -} - -impl Request for EntryRequest { - fn run( - &self, - request_context: RunRequestContext, - ) -> Result { - // TODO: Handle globs and directories - let mut entry_path = PathBuf::from(self.entry.clone()); - if entry_path.is_relative() { - entry_path = request_context.project_root.join(entry_path); - }; - - if request_context.file_system().is_file(&entry_path) { - return Ok(ResultAndInvalidations { - result: RequestResult::Entry(EntryRequestOutput { - entries: vec![Entry { - file_path: entry_path, - target: None, - }], - }), - // TODO: invalidations - invalidations: vec![], - }); - } - - Err(anyhow!("Unknown entry: {}", self.entry)) - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - - use crate::test_utils::{request_tracker, RequestTrackerTestOptions}; - - use super::*; - - #[test] - fn returns_error_when_entry_is_not_found() { - let request = EntryRequest { - entry: String::from("src/a.js"), - }; - - let entry = request_tracker(RequestTrackerTestOptions::default()).run_request(request); - - assert_eq!( - entry.map_err(|e| e.to_string()), - Err(String::from("Unknown entry: src/a.js")) - ) - } - - #[test] - fn returns_file_entry_from_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("parcel"); - let request = EntryRequest { - entry: String::from("src/a.js"), - }; - - let entry_path = project_root.join("src").join("a.js"); - - fs.write_file(&entry_path, String::default()); - - let entry = request_tracker(RequestTrackerTestOptions { - fs, - project_root: project_root.clone(), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - entry.map_err(|e| e.to_string()), - Ok(RequestResult::Entry(EntryRequestOutput { - entries: vec![Entry { - file_path: entry_path, - target: None, - }] - })) - ); - } - - #[test] - fn returns_file_entry_from_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - - #[cfg(not(target_os = "windows"))] - let root = PathBuf::from(std::path::MAIN_SEPARATOR_STR); - - #[cfg(target_os = "windows")] - let root = PathBuf::from("c:\\windows"); - - let entry_path = root.join("src").join("a.js"); - let request = EntryRequest { - entry: root.join("src/a.js").to_string_lossy().into_owned(), - }; - - fs.write_file(&entry_path, String::default()); - - let entry = request_tracker(RequestTrackerTestOptions { - fs, - project_root: PathBuf::from("parcel"), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - entry.map_err(|e| e.to_string()), - Ok(RequestResult::Entry(EntryRequestOutput { - entries: vec![Entry { - file_path: entry_path, - target: None, - }] - })) - ); - } -} diff --git a/crates/parcel/src/requests/path_request.rs b/crates/parcel/src/requests/path_request.rs deleted file mode 100644 index c810a9cdda0..00000000000 --- a/crates/parcel/src/requests/path_request.rs +++ /dev/null @@ -1,404 +0,0 @@ -use std::hash::Hash; -use std::path::PathBuf; -use std::sync::Arc; - -use parcel_core::diagnostic_error; -use parcel_core::plugin::BuildProgressEvent; -use parcel_core::plugin::ReporterEvent; -use parcel_core::plugin::Resolution; -use parcel_core::plugin::ResolveContext; -use parcel_core::plugin::ResolvedResolution; -use parcel_core::plugin::ResolvingEvent; -use parcel_core::types::Dependency; -use parcel_resolver::parse_scheme; - -use crate::request_tracker::Request; -use crate::request_tracker::ResultAndInvalidations; -use crate::request_tracker::RunRequestContext; -use crate::request_tracker::RunRequestError; - -use super::RequestResult; - -#[derive(Hash, Debug)] -pub struct PathRequest { - pub dependency: Arc, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum PathRequestOutput { - Excluded, - Resolved { - can_defer: bool, - path: PathBuf, - code: Option, - pipeline: Option, - query: Option, - side_effects: bool, - }, -} - -// TODO tracing, dev deps -impl Request for PathRequest { - fn run( - &self, - request_context: RunRequestContext, - ) -> Result { - request_context.report(ReporterEvent::BuildProgress(BuildProgressEvent::Resolving( - ResolvingEvent { - dependency: Arc::clone(&self.dependency), - }, - ))); - - let (parsed_pipeline, specifier) = parse_scheme(&self.dependency.specifier) - .and_then(|(pipeline, specifier)| { - if request_context - .plugins() - .named_pipelines() - .contains(&String::from(pipeline.as_ref())) - { - Ok((Some(pipeline.to_string()), specifier)) - } else { - Err(()) - } - }) - .unwrap_or((None, self.dependency.specifier.as_ref())); - - let mut invalidations = Vec::new(); - - for resolver in request_context.plugins().resolvers()?.iter() { - let resolved = resolver.resolve(ResolveContext { - dependency: Arc::clone(&self.dependency), - pipeline: parsed_pipeline.clone(), - specifier: String::from(specifier), - })?; - - invalidations.extend(resolved.invalidations); - - match resolved.resolution { - Resolution::Unresolved => continue, - Resolution::Excluded => { - return Ok(ResultAndInvalidations { - invalidations: Vec::new(), - result: RequestResult::Path(PathRequestOutput::Excluded), - }) - } - Resolution::Resolved(ResolvedResolution { - can_defer, - code, - file_path, - meta: _meta, - pipeline, - priority: _priority, - query, - side_effects, - }) => { - if !file_path.is_absolute() { - return Err(diagnostic_error!( - "{:?} must return an absolute path, but got {}", - resolver, - file_path.display() - )); - } - - // TODO resolution.diagnostics - // TODO Set dependency meta and priority - - return Ok(ResultAndInvalidations { - invalidations, - result: RequestResult::Path(PathRequestOutput::Resolved { - can_defer, - code, - path: file_path, - pipeline: pipeline - .or(parsed_pipeline) - .or(self.dependency.pipeline.clone()), - query, - side_effects, - }), - }); - } - }; - } - - if self.dependency.is_optional { - return Ok(ResultAndInvalidations { - invalidations, - result: RequestResult::Path(PathRequestOutput::Excluded), - }); - } - - let resolve_from = self - .dependency - .resolve_from - .as_ref() - .or(self.dependency.source_path.as_ref()); - - match resolve_from { - None => Err(diagnostic_error!( - "Failed to resolve {}", - self.dependency.specifier - )), - Some(from) => Err(diagnostic_error!( - "Failed to resolve {} from {}", - self.dependency.specifier, - from.display() - )), - } - } -} - -#[cfg(test)] -mod tests { - use std::fmt::Debug; - - use parcel_core::plugin::{ - composite_reporter_plugin::CompositeReporterPlugin, Resolved, ResolverPlugin, - }; - - use crate::{ - plugins::{MockPlugins, PluginsRef}, - test_utils::{request_tracker, RequestTrackerTestOptions}, - }; - - use super::*; - - macro_rules! test_plugins { - ($resolvers:expr) => {{ - let mut plugins = MockPlugins::new(); - - plugins.expect_named_pipelines().returning(|| Vec::new()); - - plugins - .expect_reporter() - .returning(|| Arc::new(CompositeReporterPlugin::default())); - - plugins.expect_resolvers().returning(move || Ok($resolvers)); - - let plugins: PluginsRef = Arc::new(plugins); - - Some(plugins) - }}; - } - - #[derive(Debug, Hash)] - struct ExcludedResolverPlugin {} - - impl ResolverPlugin for ExcludedResolverPlugin { - fn resolve(&self, _ctx: ResolveContext) -> Result { - Ok(Resolved { - invalidations: Vec::new(), - resolution: Resolution::Excluded, - }) - } - } - - struct ResolvedResolverPlugin { - resolution: ResolvedResolution, - } - - impl Debug for ResolvedResolverPlugin { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ResolvedResolverPlugin") - } - } - - impl Hash for ResolvedResolverPlugin { - fn hash(&self, _state: &mut H) {} - } - - impl ResolverPlugin for ResolvedResolverPlugin { - fn resolve(&self, _ctx: ResolveContext) -> Result { - Ok(Resolved { - invalidations: Vec::new(), - resolution: Resolution::Resolved(self.resolution.clone()), - }) - } - } - - #[derive(Debug, Hash)] - struct UnresolvedResolverPlugin {} - - impl ResolverPlugin for UnresolvedResolverPlugin { - fn resolve(&self, _ctx: ResolveContext) -> Result { - Ok(Resolved { - invalidations: Vec::new(), - resolution: Resolution::Unresolved, - }) - } - } - - #[test] - fn returns_excluded_resolution() { - let request = PathRequest { - dependency: Arc::new(Dependency::default()), - }; - - let resolution = request_tracker(RequestTrackerTestOptions { - plugins: test_plugins!(vec![Box::new(ExcludedResolverPlugin {})]), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - resolution.map_err(|e| e.to_string()), - Ok(RequestResult::Path(PathRequestOutput::Excluded)) - ); - } - - #[test] - fn returns_an_error_when_resolved_file_path_is_not_absolute() { - let request = PathRequest { - dependency: Arc::new(Dependency::default()), - }; - - let resolution = request_tracker(RequestTrackerTestOptions { - plugins: test_plugins!(vec![Box::new(ResolvedResolverPlugin { - resolution: ResolvedResolution { - file_path: PathBuf::from("./"), - ..ResolvedResolution::default() - }, - })]), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - resolution.map_err(|e| e.to_string()), - Err(String::from( - "ResolvedResolverPlugin must return an absolute path, but got ./" - )) - ); - } - - #[test] - fn returns_the_first_resolved_resolution() { - #[cfg(not(target_os = "windows"))] - let root = PathBuf::from(std::path::MAIN_SEPARATOR_STR); - - #[cfg(target_os = "windows")] - let root = PathBuf::from("c:\\windows"); - - let request = PathRequest { - dependency: Arc::new(Dependency::default()), - }; - - let path = root.join("a.js"); - - let resolution = request_tracker(RequestTrackerTestOptions { - plugins: test_plugins!(vec![ - Box::new(UnresolvedResolverPlugin {}), - Box::new(ResolvedResolverPlugin { - resolution: ResolvedResolution { - file_path: root.join("a.js"), - ..ResolvedResolution::default() - }, - }), - Box::new(ResolvedResolverPlugin { - resolution: ResolvedResolution { - file_path: root.join("b.js"), - ..ResolvedResolution::default() - }, - }), - ]), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - resolution.map_err(|e| e.to_string()), - Ok(RequestResult::Path(PathRequestOutput::Resolved { - can_defer: false, - code: None, - path, - pipeline: None, - query: None, - side_effects: false - })) - ); - } - - mod when_all_resolvers_return_unresolved { - use super::*; - - #[test] - fn returns_an_excluded_resolution_when_the_dependency_is_optional() { - let request = PathRequest { - dependency: Arc::new(Dependency { - is_optional: true, - specifier: String::from("a.js"), - ..Default::default() - }), - }; - - let resolution = request_tracker(RequestTrackerTestOptions { - plugins: test_plugins!(vec![Box::new(UnresolvedResolverPlugin {})]), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - resolution.map_err(|e| e.to_string()), - Ok(RequestResult::Path(PathRequestOutput::Excluded)) - ); - } - - #[test] - fn returns_an_error_when_the_dependency_is_required() { - let assert_error = |dependency: Dependency, error: &str| { - let request = PathRequest { - dependency: Arc::new(Dependency { - is_optional: false, - ..dependency - }), - }; - - let resolution = request_tracker(RequestTrackerTestOptions { - plugins: test_plugins!(vec![Box::new(UnresolvedResolverPlugin {})]), - ..RequestTrackerTestOptions::default() - }) - .run_request(request); - - assert_eq!( - resolution.map_err(|e| e.to_string()), - Err(String::from(error)) - ); - }; - - assert_error( - Dependency { - specifier: String::from("a.js"), - ..Dependency::default() - }, - "Failed to resolve a.js", - ); - - assert_error( - Dependency { - resolve_from: Some(PathBuf::from("rf.js")), - specifier: String::from("a.js"), - ..Dependency::default() - }, - "Failed to resolve a.js from rf.js", - ); - - assert_error( - Dependency { - source_path: Some(PathBuf::from("sp.js")), - specifier: String::from("a.js"), - ..Dependency::default() - }, - "Failed to resolve a.js from sp.js", - ); - - assert_error( - Dependency { - resolve_from: Some(PathBuf::from("rf.js")), - source_path: Some(PathBuf::from("sp.js")), - specifier: String::from("a.js"), - ..Dependency::default() - }, - "Failed to resolve a.js from rf.js", - ); - } - } -} diff --git a/crates/parcel/src/requests/target_request.rs b/crates/parcel/src/requests/target_request.rs deleted file mode 100644 index 50b8ada363a..00000000000 --- a/crates/parcel/src/requests/target_request.rs +++ /dev/null @@ -1,1259 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; - -use package_json::BrowserField; -use package_json::BrowsersList; -use package_json::BuiltInTargetDescriptor; -use package_json::ModuleFormat; -use package_json::PackageJson; -use package_json::SourceField; -use package_json::SourceMapField; -use package_json::TargetDescriptor; -use parcel_core::config_loader::ConfigFile; -use parcel_core::diagnostic_error; -use parcel_core::types::engines::Engines; -use parcel_core::types::BuildMode; -use parcel_core::types::CodeFrame; -use parcel_core::types::DefaultTargetOptions; -use parcel_core::types::Diagnostic; -use parcel_core::types::DiagnosticBuilder; -use parcel_core::types::Environment; -use parcel_core::types::EnvironmentContext; -use parcel_core::types::ErrorKind; -use parcel_core::types::OutputFormat; -use parcel_core::types::SourceType; -use parcel_core::types::Target; -use parcel_core::types::TargetSourceMapOptions; -use parcel_resolver::IncludeNodeModules; - -use crate::request_tracker::Request; -use crate::request_tracker::ResultAndInvalidations; -use crate::request_tracker::RunRequestContext; -use crate::request_tracker::RunRequestError; - -use super::entry_request::Entry; -use super::RequestResult; - -mod package_json; - -/// Infers how and where source code is outputted -/// -/// Targets will be generated from the project package.json file and input Parcel options. -/// -#[derive(Debug)] -pub struct TargetRequest { - pub default_target_options: DefaultTargetOptions, - pub entry: Entry, - pub env: Option>, - pub mode: BuildMode, -} - -impl Hash for TargetRequest { - fn hash(&self, state: &mut H) { - self.default_target_options.hash(state); - self.entry.hash(state); - self.mode.hash(state); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct TargetRequestOutput { - pub entry: PathBuf, - pub targets: Vec, -} - -struct BuiltInTarget<'a> { - descriptor: BuiltInTargetDescriptor, - dist: Option, - name: &'a str, -} - -struct CustomTarget<'a> { - descriptor: &'a TargetDescriptor, - name: &'a str, -} - -impl TargetRequest { - fn builtin_browser_target( - &self, - descriptor: Option, - dist: Option, - name: Option, - ) -> BuiltInTarget { - BuiltInTarget { - descriptor: descriptor.unwrap_or_else(|| { - BuiltInTargetDescriptor::TargetDescriptor(TargetDescriptor { - context: Some(EnvironmentContext::Browser), - ..builtin_target_descriptor() - }) - }), - dist: dist.and_then(|browser| match browser { - BrowserField::EntryPoint(entrypoint) => Some(entrypoint.clone()), - BrowserField::ReplacementBySpecifier(replacements) => { - name.and_then(|name| replacements.get(&name).map(|v| v.into())) - } - }), - name: "browser", - } - } - - fn builtin_main_target( - &self, - descriptor: Option, - dist: Option, - ) -> BuiltInTarget { - BuiltInTarget { - descriptor: descriptor.unwrap_or_else(|| { - BuiltInTargetDescriptor::TargetDescriptor(TargetDescriptor { - context: Some(EnvironmentContext::Node), - ..builtin_target_descriptor() - }) - }), - dist, - name: "main", - } - } - - fn builtin_module_target( - &self, - descriptor: Option, - dist: Option, - ) -> BuiltInTarget { - BuiltInTarget { - descriptor: descriptor.unwrap_or_else(|| { - BuiltInTargetDescriptor::TargetDescriptor(TargetDescriptor { - context: Some(EnvironmentContext::Node), - ..builtin_target_descriptor() - }) - }), - dist, - name: "module", - } - } - - fn builtin_types_target( - &self, - descriptor: Option, - dist: Option, - ) -> BuiltInTarget { - BuiltInTarget { - descriptor: descriptor.unwrap_or_else(|| { - BuiltInTargetDescriptor::TargetDescriptor(TargetDescriptor { - context: Some(EnvironmentContext::Node), - ..builtin_target_descriptor() - }) - }), - dist, - name: "types", - } - } - - fn infer_environment_context(&self, package_json: &PackageJson) -> EnvironmentContext { - // If there is a separate `browser` target, or an `engines.node` field but no browser - // targets, then the target refers to node, otherwise browser. - if package_json.browser.is_some() || package_json.targets.browser.is_some() { - if package_json - .engines - .as_ref() - .is_some_and(|e| e.node.is_some() && e.browsers.is_empty()) - { - return EnvironmentContext::Node; - } else { - return EnvironmentContext::Browser; - } - } - - if package_json - .engines - .as_ref() - .is_some_and(|e| e.node.is_some()) - { - return EnvironmentContext::Node; - } - - EnvironmentContext::Browser - } - - fn infer_output_format( - &self, - dist_entry: &Option, - package_json: &ConfigFile, - target: &TargetDescriptor, - ) -> Result, anyhow::Error> { - let ext = dist_entry - .as_ref() - .and_then(|e| e.extension()) - .unwrap_or_default() - .to_str(); - - let inferred_output_format = match ext { - Some("cjs") => Some(OutputFormat::CommonJS), - Some("mjs") => Some(OutputFormat::EsModule), - Some("js") => package_json - .contents - .module_format - .as_ref() - .and_then(|format| match format { - ModuleFormat::CommonJS => Some(OutputFormat::CommonJS), - ModuleFormat::Module => Some(OutputFormat::EsModule), - }), - _ => None, - }; - - if let Some(inferred_output_format) = inferred_output_format { - if let Some(output_format) = target.output_format { - if output_format != inferred_output_format { - return Err(diagnostic_error!(DiagnosticBuilder::default() - .code_frames(vec![CodeFrame::from(package_json)]) - .message(format!( - "Declared output format {output_format} does not match expected output format {inferred_output_format}", - )))); - } - } - } - - Ok(inferred_output_format) - } - - fn load_package_json( - &self, - request_context: RunRequestContext, - ) -> Result, anyhow::Error> { - // TODO Invalidations - let mut package_json = match request_context.config().load_package_json::() { - Err(err) => { - let diagnostic = err.downcast_ref::(); - - if diagnostic.is_some_and(|d| d.kind != ErrorKind::NotFound) { - return Err(err); - } - - ConfigFile { - contents: PackageJson::default(), - path: PathBuf::default(), - raw: String::default(), - } - } - Ok(pkg) => pkg, - }; - - if package_json - .contents - .engines - .as_ref() - .is_some_and(|e| !e.browsers.is_empty()) - { - return Ok(package_json); - } - - let env = self - .env - .as_ref() - .and_then(|env| env.get("BROWSERSLIST_ENV").or_else(|| env.get("NODE_ENV"))) - .map(|e| e.to_owned()) - .unwrap_or_else(|| self.mode.to_string()); - - match package_json.contents.browserslist.clone() { - // TODO Process browserslist config file - None => {} - Some(browserslist) => { - let browserslist = match browserslist { - BrowsersList::Browsers(browsers) => browsers, - BrowsersList::BrowsersByEnv(browsers_by_env) => browsers_by_env - .get(&env) - .map(|b| b.clone()) - .unwrap_or_default(), - }; - - package_json.contents.engines = Some(Engines { - browsers: Engines::from_browserslist(browserslist), - ..match package_json.contents.engines { - None => Engines::default(), - Some(engines) => engines, - } - }); - } - }; - - Ok(package_json) - } - - fn resolve_package_targets( - &self, - request_context: RunRequestContext, - ) -> Result>, anyhow::Error> { - let package_json = self.load_package_json(request_context)?; - let mut targets: Vec> = Vec::new(); - - let builtin_targets = [ - self.builtin_browser_target( - package_json.contents.targets.browser.clone(), - package_json.contents.browser.clone(), - package_json.contents.name.clone(), - ), - self.builtin_main_target( - package_json.contents.targets.main.clone(), - package_json.contents.main.clone(), - ), - self.builtin_module_target( - package_json.contents.targets.module.clone(), - package_json.contents.module.clone(), - ), - self.builtin_types_target( - package_json.contents.targets.types.clone(), - package_json.contents.types.clone(), - ), - ]; - - for builtin_target in builtin_targets { - if builtin_target.dist.is_none() { - continue; - } - - match builtin_target.descriptor { - BuiltInTargetDescriptor::Disabled(_disabled) => continue, - BuiltInTargetDescriptor::TargetDescriptor(builtin_target_descriptor) => { - targets.push(self.target_from_descriptor( - builtin_target.dist, - &package_json, - builtin_target_descriptor, - builtin_target.name, - )?); - } - } - } - - let custom_targets = package_json - .contents - .targets - .custom_targets - .iter() - .map(|(name, descriptor)| CustomTarget { descriptor, name }); - - for custom_target in custom_targets { - let mut dist = None; - if let Some(value) = package_json.contents.fields.get(custom_target.name) { - match value { - serde_json::Value::String(str) => { - dist = Some(PathBuf::from(str)); - } - _ => { - return Err(diagnostic_error!(DiagnosticBuilder::default() - .code_frames(vec![CodeFrame::from(&package_json)]) - .message(format!("Invalid path for target {}", custom_target.name)))); - } - } - } - - targets.push(self.target_from_descriptor( - dist, - &package_json, - custom_target.descriptor.clone(), - &custom_target.name, - )?); - } - - if targets.is_empty() { - let context = self.infer_environment_context(&package_json.contents); - - targets.push(Some(Target { - dist_dir: self - .default_target_options - .dist_dir - .clone() - .unwrap_or_else(|| default_dist_dir(&package_json.path)), - dist_entry: None, - env: Arc::new(Environment { - context, - engines: package_json - .contents - .engines - .unwrap_or_else(|| self.default_target_options.engines.clone()), - include_node_modules: IncludeNodeModules::from(context), - is_library: self.default_target_options.is_library, - loc: None, - output_format: self - .default_target_options - .output_format - .unwrap_or_else(|| fallback_output_format(context)), - should_optimize: self.default_target_options.should_optimize, - should_scope_hoist: self.default_target_options.should_scope_hoist - && self.mode == BuildMode::Production - && !self.default_target_options.is_library, - source_map: self - .default_target_options - .source_maps - .then(|| TargetSourceMapOptions::default()), - source_type: SourceType::Module, - }), - loc: None, - name: String::from("default"), - public_url: self.default_target_options.public_url.clone(), - })); - } - - Ok(targets) - } - - fn skip_target(&self, target_name: &str, source: &Option) -> bool { - // We skip targets if they have a descriptor.source that does not match the current - // exclusiveTarget. They will be handled by a separate resolvePackageTargets call from their - // Entry point but with exclusiveTarget set. - match self.entry.target.as_ref() { - None => source.is_some(), - Some(exclusive_target) => target_name != exclusive_target, - } - } - - fn target_from_descriptor( - &self, - dist: Option, - package_json: &ConfigFile, - target_descriptor: TargetDescriptor, - target_name: &str, - ) -> Result, anyhow::Error> { - if self.skip_target(&target_name, &target_descriptor.source) { - return Ok(None); - } - - // TODO LOC - let context = target_descriptor - .context - .unwrap_or_else(|| self.infer_environment_context(&package_json.contents)); - - let dist_entry = target_descriptor.dist_entry.clone().or_else(|| { - dist - .as_ref() - .and_then(|d| d.file_name().map(|f| PathBuf::from(f))) - }); - - let inferred_output_format = - self.infer_output_format(&dist_entry, &package_json, &target_descriptor)?; - - let output_format = target_descriptor - .output_format - .or(self.default_target_options.output_format) - .or(inferred_output_format) - .unwrap_or_else(|| match target_name { - "browser" => OutputFormat::CommonJS, - "main" => OutputFormat::CommonJS, - "module" => OutputFormat::EsModule, - "types" => OutputFormat::CommonJS, - _ => match context { - EnvironmentContext::ElectronMain => OutputFormat::CommonJS, - EnvironmentContext::ElectronRenderer => OutputFormat::CommonJS, - EnvironmentContext::Node => OutputFormat::CommonJS, - _ => OutputFormat::Global, - }, - }); - - if target_name == "main" - && output_format == OutputFormat::EsModule - && inferred_output_format.is_some_and(|f| f != OutputFormat::EsModule) - { - return Err(diagnostic_error!(DiagnosticBuilder::default() - .code_frames(vec![CodeFrame::from(package_json)]) - .message("Output format \"esmodule\" cannot be used in the \"main\" target without a .mjs extension or \"type\": \"module\" field"))); - } - - let is_library = target_descriptor - .is_library - .unwrap_or_else(|| self.default_target_options.is_library); - - Ok(Some(Target { - dist_dir: match dist.as_ref() { - None => self - .default_target_options - .dist_dir - .clone() - .unwrap_or_else(|| default_dist_dir(&package_json.path).join(target_name)), - Some(target_dist) => { - let package_dir = package_json - .path - .parent() - .unwrap_or_else(|| &package_json.path); - let dir = target_dist - .parent() - .map(|dir| dir.strip_prefix("./").ok().unwrap_or(dir)) - .and_then(|dir| { - if dir == PathBuf::from("") { - None - } else { - Some(dir) - } - }); - - match dir { - None => PathBuf::from(package_dir), - Some(dir) => package_dir.join(dir), - } - } - }, - dist_entry, - env: Arc::new(Environment { - context, - engines: target_descriptor - .engines - .clone() - .or_else(|| package_json.contents.engines.clone()) - .unwrap_or_else(|| self.default_target_options.engines.clone()), - include_node_modules: target_descriptor - .include_node_modules - .unwrap_or_else(|| IncludeNodeModules::from(context)), - is_library, - loc: None, // TODO - output_format, - should_optimize: self.default_target_options.should_optimize - && if is_library { - // Libraries are not optimized by default, users must explicitly configure this. - target_descriptor.optimize.is_some_and(|o| o == true) - } else { - target_descriptor.optimize.is_none() - || target_descriptor.optimize.is_some_and(|o| o != false) - }, - should_scope_hoist: (is_library || self.default_target_options.should_scope_hoist) - && (target_descriptor.scope_hoist.is_none() - || target_descriptor.scope_hoist.is_some_and(|s| s != false)), - source_map: match self.default_target_options.source_maps { - false => None, - true => target_descriptor.source_map.as_ref().and_then(|s| match s { - SourceMapField::Bool(source_maps) => { - source_maps.then(|| TargetSourceMapOptions::default()) - } - SourceMapField::Options(source_maps) => Some(source_maps.clone()), - }), - }, - ..Environment::default() - }), - loc: None, // TODO - name: String::from(target_name), - public_url: target_descriptor - .public_url - .clone() - .unwrap_or(self.default_target_options.public_url.clone()), - })) - } -} - -fn builtin_target_descriptor() -> TargetDescriptor { - TargetDescriptor { - include_node_modules: Some(IncludeNodeModules::Bool(false)), - is_library: Some(true), - scope_hoist: Some(true), - ..TargetDescriptor::default() - } -} - -fn default_dist_dir(package_path: &Path) -> PathBuf { - package_path - .parent() - .unwrap_or_else(|| &package_path) - .join("dist") -} - -fn fallback_output_format(context: EnvironmentContext) -> OutputFormat { - match context { - EnvironmentContext::Node => OutputFormat::CommonJS, - EnvironmentContext::ElectronMain => OutputFormat::CommonJS, - EnvironmentContext::ElectronRenderer => OutputFormat::CommonJS, - _ => OutputFormat::Global, - } -} - -impl Request for TargetRequest { - fn run( - &self, - request_context: RunRequestContext, - ) -> Result { - // TODO options.targets, should this still be supported? - // TODO serve options - let package_targets = self.resolve_package_targets(request_context)?; - - Ok(ResultAndInvalidations { - invalidations: Vec::new(), - result: RequestResult::Target(TargetRequestOutput { - entry: self.entry.file_path.clone(), - targets: package_targets - .into_iter() - .filter_map(std::convert::identity) - .collect(), - }), - }) - } -} - -// TODO Add more tests when revisiting targets config structure -#[cfg(test)] -mod tests { - use std::{num::NonZeroU16, sync::Arc}; - - use regex::Regex; - - use parcel_core::types::{browsers::Browsers, version::Version}; - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - - use crate::test_utils::{request_tracker, RequestTrackerTestOptions}; - - use super::*; - - const BUILT_IN_TARGETS: [&str; 4] = ["browser", "main", "module", "types"]; - - fn default_target() -> Target { - Target { - dist_dir: PathBuf::from("packages/test/dist"), - env: Arc::new(Environment { - output_format: OutputFormat::Global, - ..Environment::default() - }), - name: String::from("default"), - ..Target::default() - } - } - - fn package_dir() -> PathBuf { - PathBuf::from("packages").join("test") - } - - fn targets_from_package_json(package_json: String) -> Result { - let fs = InMemoryFileSystem::default(); - let project_root = PathBuf::default(); - let package_dir = package_dir(); - - fs.write_file( - &project_root.join(&package_dir).join("package.json"), - package_json, - ); - - let request = TargetRequest { - default_target_options: DefaultTargetOptions::default(), - entry: Entry::default(), - env: None, - mode: BuildMode::Development, - }; - - request_tracker(RequestTrackerTestOptions { - search_path: project_root.join(&package_dir), - project_root, - fs: Arc::new(fs), - ..Default::default() - }) - .run_request(request) - } - - fn to_deterministic_error(error: anyhow::Error) -> String { - let re = Regex::new(r"\d+").unwrap(); - re.replace_all(&format!("{:#}", error), "\\d").into_owned() - } - - #[test] - fn returns_error_when_builtin_target_is_true() { - for builtin_target in BUILT_IN_TARGETS { - let targets = targets_from_package_json(format!( - r#"{{ "targets": {{ "{builtin_target}": true }} }}"#, - )); - - assert_eq!( - targets.map_err(to_deterministic_error), - Err(format!("data did not match any variant of untagged enum BuiltInTargetDescriptor at line \\d column \\d in {}", - package_dir().join("package.json").display() - )) - ); - } - } - - #[test] - fn returns_error_when_builtin_target_does_not_reference_expected_extension() { - for builtin_target in BUILT_IN_TARGETS { - let targets = - targets_from_package_json(format!(r#"{{ "{}": "dist/main.rs" }}"#, builtin_target)); - - assert_eq!( - targets.map_err(to_deterministic_error), - Err(format!( - "Unexpected file type \"main.rs\" in \"{}\" target at line \\d column \\d in {}", - builtin_target, - package_dir().join("package.json").display() - )) - ); - } - } - - #[test] - fn returns_error_when_builtin_target_has_global_output_format() { - for builtin_target in BUILT_IN_TARGETS { - let targets = targets_from_package_json(format!( - r#"{{ - "targets": {{ - "{builtin_target}": {{ "outputFormat": "global" }} - }} - }}"# - )); - - assert_eq!( - targets.map_err(to_deterministic_error), - Err(format!( - "The \"global\" output format is not supported in the {} target at line \\d column \\d in {}", - builtin_target, - package_dir().join("package.json").display() - )) - ); - } - } - - #[test] - fn returns_error_when_output_format_does_not_match_inferred_output_format() { - let assert_error = |ext, module_format: Option<&str>, output_format| { - let targets = targets_from_package_json(format!( - r#" - {{ - {} - "custom": "dist/custom.{ext}", - "targets": {{ - "custom": {{ - "outputFormat": "{output_format}" - }} - }} - }} - "#, - module_format.map_or_else( - || String::default(), - |module_format| format!(r#""type": "{module_format}","#) - ), - )); - - assert_eq!( - targets.map_err(|err| err.to_string()), - Err(format!( - "Declared output format {output_format} does not match expected output format {}", - if output_format == OutputFormat::CommonJS { - "esmodule" - } else { - "commonjs" - } - )) - ); - }; - - assert_error("cjs", None, OutputFormat::EsModule); - assert_error("cjs", Some("module"), OutputFormat::EsModule); - - assert_error("js", Some("commonjs"), OutputFormat::EsModule); - assert_error("js", Some("module"), OutputFormat::CommonJS); - - assert_error("mjs", None, OutputFormat::CommonJS); - assert_error("mjs", Some("commonjs"), OutputFormat::CommonJS); - } - - #[test] - fn returns_error_when_scope_hoisting_disabled_for_library_targets() { - let assert_error = |name, package_json| { - let targets = targets_from_package_json(package_json); - - assert_eq!( - targets.map_err(to_deterministic_error), - Err(format!( - "Scope hoisting cannot be disabled for \"{}\" library target at line \\d column \\d in {}", - name, - package_dir().join("package.json").display() - )) - ); - }; - - for target in BUILT_IN_TARGETS { - assert_error( - target, - format!( - r#" - {{ - "{target}": "dist/target.{ext}", - "targets": {{ - "{target}": {{ - "isLibrary": true, - "scopeHoist": false - }} - }} - }} - "#, - ext = if target == "types" { "ts" } else { "js" }, - ), - ); - } - - assert_error( - "custom", - String::from( - r#" - { - "targets": { - "custom": { - "isLibrary": true, - "scopeHoist": false - } - } - } - "#, - ), - ); - } - - #[test] - fn returns_default_target_when_package_json_is_not_found() { - let request = TargetRequest { - default_target_options: DefaultTargetOptions::default(), - entry: Entry::default(), - env: None, - mode: BuildMode::Development, - }; - - let targets = request_tracker(RequestTrackerTestOptions::default()).run_request(request); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: default_dist_dir(&PathBuf::default()), - ..default_target() - }], - })) - ); - } - - #[test] - fn returns_default_target_when_builtin_targets_are_disabled() { - for builtin_target in BUILT_IN_TARGETS { - let targets = targets_from_package_json(format!( - r#"{{ "targets": {{ "{builtin_target}": false }} }}"# - )); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![default_target()] - })) - ); - } - } - - #[test] - fn returns_default_target_when_no_targets_are_specified() { - let targets = targets_from_package_json(String::from("{}")); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![default_target()] - })) - ); - } - - fn builtin_default_env() -> Environment { - Environment { - include_node_modules: IncludeNodeModules::Bool(false), - is_library: true, - should_optimize: false, - should_scope_hoist: true, - ..Environment::default() - } - } - - #[test] - fn returns_builtin_browser_target() { - let targets = targets_from_package_json(String::from(r#"{ "browser": "build/browser.js" }"#)); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("build"), - dist_entry: Some(PathBuf::from("browser.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Browser, - output_format: OutputFormat::CommonJS, - ..builtin_default_env() - }), - name: String::from("browser"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_builtin_main_target() { - let targets = targets_from_package_json(String::from(r#"{ "main": "./build/main.js" }"#)); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("build"), - dist_entry: Some(PathBuf::from("main.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::CommonJS, - ..builtin_default_env() - }), - name: String::from("main"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_builtin_module_target() { - let targets = targets_from_package_json(String::from(r#"{ "module": "module.js" }"#)); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir(), - dist_entry: Some(PathBuf::from("module.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::EsModule, - ..builtin_default_env() - }), - name: String::from("module"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_builtin_types_target() { - let targets = targets_from_package_json(String::from(r#"{ "types": "./types.d.ts" }"#)); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir(), - dist_entry: Some(PathBuf::from("types.d.ts")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::CommonJS, - ..builtin_default_env() - }), - name: String::from("types"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_builtin_targets() { - let targets = targets_from_package_json(String::from( - r#" - { - "browser": "build/browser.js", - "main": "./build/main.js", - "module": "module.js", - "types": "./types.d.ts", - "browserslist": ["chrome 20"] - } - "#, - )); - - let env = || Environment { - engines: Engines { - browsers: Browsers { - chrome: Some(Version::new(NonZeroU16::new(20).unwrap(), 0)), - ..Browsers::default() - }, - ..Engines::default() - }, - ..builtin_default_env() - }; - - let package_dir = package_dir(); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![ - Target { - dist_dir: package_dir.join("build"), - dist_entry: Some(PathBuf::from("browser.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Browser, - output_format: OutputFormat::CommonJS, - ..env() - }), - name: String::from("browser"), - ..Target::default() - }, - Target { - dist_dir: package_dir.join("build"), - dist_entry: Some(PathBuf::from("main.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::CommonJS, - ..env() - }), - name: String::from("main"), - ..Target::default() - }, - Target { - dist_dir: package_dir.clone(), - dist_entry: Some(PathBuf::from("module.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::EsModule, - ..env() - }), - name: String::from("module"), - ..Target::default() - }, - Target { - dist_dir: package_dir, - dist_entry: Some(PathBuf::from("types.d.ts")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - output_format: OutputFormat::CommonJS, - ..env() - }), - name: String::from("types"), - ..Target::default() - }, - ] - })) - ); - } - - #[test] - fn returns_custom_targets_with_defaults() { - let targets = targets_from_package_json(String::from(r#"{ "targets": { "custom": {} } } "#)); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("dist").join("custom"), - dist_entry: None, - env: Arc::new(Environment { - context: EnvironmentContext::Browser, - is_library: false, - output_format: OutputFormat::Global, - should_optimize: false, - should_scope_hoist: false, - ..Environment::default() - }), - name: String::from("custom"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_custom_targets() { - let targets = targets_from_package_json(String::from( - r#" - { - "custom": "dist/custom.js", - "targets": { - "custom": { - "context": "node", - "includeNodeModules": true, - "outputFormat": "commonjs" - } - } - } - "#, - )); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("dist"), - dist_entry: Some(PathBuf::from("custom.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - include_node_modules: IncludeNodeModules::Bool(true), - is_library: false, - output_format: OutputFormat::CommonJS, - ..Environment::default() - }), - name: String::from("custom"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_inferred_custom_browser_target() { - let targets = targets_from_package_json(String::from( - r#" - { - "custom": "dist/custom.js", - "browserslist": ["chrome 20", "firefox > 1"], - "targets": { - "custom": {} - } - } - "#, - )); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("dist"), - dist_entry: Some(PathBuf::from("custom.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Browser, - engines: Engines { - browsers: Browsers { - chrome: Some(Version::new(NonZeroU16::new(20).unwrap(), 0)), - firefox: Some(Version::new(NonZeroU16::new(2).unwrap(), 0)), - ..Browsers::default() - }, - ..Engines::default() - }, - include_node_modules: IncludeNodeModules::Bool(true), - output_format: OutputFormat::Global, - ..Environment::default() - }), - name: String::from("custom"), - ..Target::default() - }] - })) - ); - } - - #[test] - fn returns_inferred_custom_node_target() { - let assert_targets = |targets: Result, engines| { - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("dist"), - dist_entry: Some(PathBuf::from("custom.js")), - env: Arc::new(Environment { - context: EnvironmentContext::Node, - engines, - include_node_modules: IncludeNodeModules::Bool(false), - output_format: OutputFormat::CommonJS, - ..Environment::default() - }), - name: String::from("custom"), - ..Target::default() - }] - })) - ); - }; - - assert_targets( - targets_from_package_json(String::from( - r#" - { - "custom": "dist/custom.js", - "engines": { "node": "^1.0.0" }, - "targets": { "custom": {} } - } - "#, - )), - Engines { - node: Some(Version::new(NonZeroU16::new(1).unwrap(), 0)), - ..Engines::default() - }, - ); - - assert_targets( - targets_from_package_json(String::from( - r#" - { - "custom": "dist/custom.js", - "engines": { "node": "^1.0.0" }, - "browserslist": ["chrome 20"], - "targets": { "custom": {} } - } - "#, - )), - Engines { - browsers: Browsers { - chrome: Some(Version::new(NonZeroU16::new(20).unwrap(), 0)), - ..Browsers::default() - }, - node: Some(Version::new(NonZeroU16::new(1).unwrap(), 0)), - ..Engines::default() - }, - ); - } - - #[test] - fn returns_custom_target_when_output_format_matches_inferred_output_format() { - let assert_targets = |ext, module_format: Option, output_format| { - let targets = targets_from_package_json(format!( - r#" - {{ - {} - "custom": "dist/custom.{ext}", - "targets": {{ - "custom": {{ - "outputFormat": "{output_format}" - }} - }} - }} - "#, - module_format.map_or_else( - || String::default(), - |module_format| format!(r#""type": "{module_format}","#) - ), - )); - - assert_eq!( - targets.map_err(|e| e.to_string()), - Ok(RequestResult::Target(TargetRequestOutput { - entry: PathBuf::default(), - targets: vec![Target { - dist_dir: package_dir().join("dist"), - dist_entry: Some(PathBuf::from(format!("custom.{ext}"))), - env: Arc::new(Environment { - output_format, - ..Environment::default() - }), - name: String::from("custom"), - ..Target::default() - }], - })) - ); - }; - - assert_targets("cjs", None, OutputFormat::CommonJS); - assert_targets("cjs", Some(ModuleFormat::CommonJS), OutputFormat::CommonJS); - assert_targets("cjs", Some(ModuleFormat::Module), OutputFormat::CommonJS); - - assert_targets("js", None, OutputFormat::CommonJS); - assert_targets("js", Some(ModuleFormat::CommonJS), OutputFormat::CommonJS); - - assert_targets("js", None, OutputFormat::EsModule); - assert_targets("js", Some(ModuleFormat::Module), OutputFormat::EsModule); - - assert_targets("mjs", None, OutputFormat::EsModule); - assert_targets("mjs", Some(ModuleFormat::CommonJS), OutputFormat::EsModule); - assert_targets("mjs", Some(ModuleFormat::Module), OutputFormat::EsModule); - } -} diff --git a/crates/parcel/src/requests/target_request/package_json.rs b/crates/parcel/src/requests/target_request/package_json.rs deleted file mode 100644 index 12f300c2eb5..00000000000 --- a/crates/parcel/src/requests/target_request/package_json.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::collections::HashMap; -use std::ffi::OsStr; -use std::fmt::Display; -use std::path::PathBuf; - -use parcel_core::types::engines::Engines; -use parcel_core::types::EnvironmentContext; -use parcel_core::types::OutputFormat; -use parcel_core::types::TargetSourceMapOptions; -use parcel_resolver::IncludeNodeModules; -use serde::Deserialize; -use serde::Deserializer; - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum BrowserField { - EntryPoint(PathBuf), - // TODO false value - ReplacementBySpecifier(HashMap), -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum BrowsersList { - Browsers(Vec), - BrowsersByEnv(HashMap>), -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum BuiltInTargetDescriptor { - Disabled(serde_bool::False), - TargetDescriptor(TargetDescriptor), -} - -#[derive(Debug, Clone, Default, Deserialize)] -#[serde(default, rename_all = "camelCase")] -pub struct TargetDescriptor { - pub context: Option, - pub dist_dir: Option, - pub dist_entry: Option, - pub engines: Option, - pub include_node_modules: Option, - pub is_library: Option, - pub optimize: Option, - pub output_format: Option, - pub public_url: Option, - pub scope_hoist: Option, - pub source: Option, - pub source_map: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ModuleFormat { - CommonJS, - Module, -} - -impl Display for ModuleFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ModuleFormat::CommonJS => write!(f, "commonjs"), - ModuleFormat::Module => write!(f, "module"), - } - } -} - -#[derive(Debug, Default, Deserialize)] -pub struct PackageJson { - pub name: Option, - - #[serde(rename = "type")] - pub module_format: Option, - - #[serde(default, deserialize_with = "browser_field")] - pub browser: Option, - - #[serde(default, deserialize_with = "main_field")] - pub main: Option, - - #[serde(default, deserialize_with = "module_field")] - pub module: Option, - - #[serde(default, deserialize_with = "types_field")] - pub types: Option, - - #[serde(default)] - pub engines: Option, - - pub browserslist: Option, - - #[serde(default)] - pub targets: TargetsField, - - #[serde(flatten)] - pub fields: HashMap, -} - -#[derive(Debug, Clone, Deserialize)] -pub enum SourceField { - #[allow(unused)] - Source(String), - #[allow(unused)] - Sources(Vec), -} - -#[derive(Debug, Clone, Deserialize)] -pub enum SourceMapField { - Bool(bool), - Options(TargetSourceMapOptions), -} - -fn browser_field<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let browser = Option::::deserialize(deserializer)?; - - if let Some(browser_field) = browser.as_ref() { - let allowed_extensions = vec!["cjs", "js", "mjs"]; - match browser_field { - BrowserField::EntryPoint(dist) => { - validate_extension::("browser", &dist, &allowed_extensions)?; - } - BrowserField::ReplacementBySpecifier(replacements) => { - for dist in replacements.values() { - validate_extension::("browser", &dist, &allowed_extensions)?; - } - } - }; - } - - Ok(browser) -} - -#[derive(Debug, Default, Deserialize)] -pub struct TargetsField { - #[serde(default, deserialize_with = "browser_target")] - pub browser: Option, - - #[serde(default, deserialize_with = "main_target")] - pub main: Option, - - #[serde(default, deserialize_with = "module_target")] - pub module: Option, - - #[serde(default, deserialize_with = "types_target")] - pub types: Option, - - #[serde(flatten)] - #[serde(deserialize_with = "custom_targets")] - pub custom_targets: HashMap, -} - -fn browser_target<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_target(deserializer, "browser") -} - -fn custom_targets<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - // TODO Consider refactoring to a visitor for better performance - let targets: HashMap = HashMap::deserialize(deserializer)?; - - for (target, target_descriptor) in targets.iter() { - validate_scope_hoisting::(target, target_descriptor)?; - } - - Ok(targets) -} - -fn main_field<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_dist(deserializer, "main", vec!["cjs", "mjs", "js"]) -} - -fn main_target<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_target(deserializer, "main") -} - -fn module_field<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_dist(deserializer, "module", vec!["js", "mjs"]) -} - -fn module_target<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_target(deserializer, "module") -} - -fn parse_builtin_dist<'de, D>( - deserializer: D, - target_name: &str, - allowed_extensions: Vec<&str>, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let builtin_dist = Option::::deserialize(deserializer)?; - - if let Some(dist) = builtin_dist.as_ref() { - validate_extension::(target_name, dist, &allowed_extensions)?; - } - - Ok(builtin_dist) -} - -fn parse_builtin_target<'de, D>( - deserializer: D, - target_name: &str, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let builtin_target = Option::::deserialize(deserializer)?; - - if let Some(target_descriptor) = builtin_target.as_ref() { - if let BuiltInTargetDescriptor::TargetDescriptor(target_descriptor) = target_descriptor { - validate_scope_hoisting::(target_name, target_descriptor)?; - - if target_descriptor - .output_format - .is_some_and(|f| f == OutputFormat::Global) - { - return Err(serde::de::Error::custom(format!( - "The \"global\" output format is not supported in the {} target", - target_name - ))); - } - } - } - - Ok(builtin_target) -} - -fn types_field<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_dist(deserializer, "types", vec!["ts"]) -} - -fn types_target<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - parse_builtin_target(deserializer, "types") -} - -fn validate_extension<'de, D>( - target: &str, - dist: &PathBuf, - allowed_extensions: &Vec<&str>, -) -> Result<(), D::Error> -where - D: Deserializer<'de>, -{ - let target_dist_ext = dist - .extension() - .unwrap_or(OsStr::new("")) - .to_string_lossy() - .into_owned(); - - if allowed_extensions.iter().all(|ext| &target_dist_ext != ext) { - return Err(serde::de::Error::custom(format!( - "Unexpected file type {:?} in \"{}\" target", - dist.file_name().unwrap_or(OsStr::new(&dist)), - target - ))); - } - - Ok(()) -} - -fn validate_scope_hoisting<'de, D>( - target: &str, - target_descriptor: &TargetDescriptor, -) -> Result<(), D::Error> -where - D: Deserializer<'de>, -{ - if target_descriptor.is_library.is_some_and(|l| l == true) - && target_descriptor.scope_hoist.is_some_and(|s| s == false) - { - return Err(serde::de::Error::custom(format!( - "Scope hoisting cannot be disabled for \"{}\" library target", - target - ))); - } - - Ok(()) -} diff --git a/crates/parcel/src/test_utils.rs b/crates/parcel/src/test_utils.rs deleted file mode 100644 index 4642fc0a42b..00000000000 --- a/crates/parcel/src/test_utils.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use parcel_config::parcel_config_fixtures::default_config; -use parcel_core::{ - config_loader::ConfigLoader, - plugin::{PluginContext, PluginLogger, PluginOptions}, - types::ParcelOptions, -}; -use parcel_filesystem::{in_memory_file_system::InMemoryFileSystem, FileSystemRef}; - -use crate::{ - plugins::{config_plugins::ConfigPlugins, PluginsRef}, - request_tracker::RequestTracker, -}; - -pub(crate) fn make_test_plugin_context() -> PluginContext { - let fs = Arc::new(InMemoryFileSystem::default()); - - PluginContext { - config: Arc::new(ConfigLoader { - fs: fs.clone(), - project_root: PathBuf::default(), - search_path: PathBuf::default(), - }), - file_system: fs.clone(), - options: Arc::new(PluginOptions::default()), - logger: PluginLogger::default(), - } -} - -pub(crate) fn config_plugins(ctx: PluginContext) -> PluginsRef { - let fixture = default_config(Arc::new(PathBuf::default())); - - Arc::new(ConfigPlugins::new(fixture.parcel_config, ctx)) -} - -pub struct RequestTrackerTestOptions { - pub fs: FileSystemRef, - pub plugins: Option, - pub project_root: PathBuf, - pub search_path: PathBuf, - pub parcel_options: ParcelOptions, -} - -impl Default for RequestTrackerTestOptions { - fn default() -> Self { - Self { - fs: Arc::new(InMemoryFileSystem::default()), - plugins: None, - project_root: PathBuf::default(), - search_path: PathBuf::default(), - parcel_options: ParcelOptions::default(), - } - } -} - -pub(crate) fn request_tracker(options: RequestTrackerTestOptions) -> RequestTracker { - let RequestTrackerTestOptions { - fs, - plugins, - project_root, - search_path, - parcel_options, - } = options; - - let config_loader = Arc::new(ConfigLoader { - fs: fs.clone(), - project_root: project_root.clone(), - search_path, - }); - - let plugins = plugins.unwrap_or_else(|| { - config_plugins(PluginContext { - config: Arc::clone(&config_loader), - file_system: fs.clone(), - options: Arc::new(PluginOptions { - core_path: parcel_options.core_path.clone(), - env: parcel_options.env.clone(), - log_level: parcel_options.log_level.clone(), - mode: parcel_options.mode.clone(), - project_root: project_root.clone(), - }), - logger: PluginLogger::default(), - }) - }); - - RequestTracker::new( - Arc::clone(&config_loader), - fs, - Arc::new(parcel_options), - plugins, - project_root, - ) -} diff --git a/crates/parcel_config/Cargo.toml b/crates/parcel_config/Cargo.toml deleted file mode 100644 index 75c7314b0d0..00000000000 --- a/crates/parcel_config/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -authors = [ - "Monica Olejniczak ", - "Devon Govett ", -] -name = "parcel_config" -version = "0.1.0" -edition = "2021" - -[dependencies] -parcel_core = { path = "../parcel_core" } -parcel_filesystem = { path = "../parcel_filesystem" } -parcel_package_manager = { path = "../parcel_package_manager" } - -derive_builder = "0.20.0" -glob-match = "0.2.1" -indexmap = { version = "2.2.6", features = ["serde", "std"] } -pathdiff = "0.2.1" -serde = { version = "1.0.123", features = ["derive", "rc"] } -serde_json5 = "0.1.0" - -[dev-dependencies] -anyhow = "1.0.82" -mockall = "0.12.1" diff --git a/crates/parcel_config/src/lib.rs b/crates/parcel_config/src/lib.rs deleted file mode 100644 index 0ddb34ce9f0..00000000000 --- a/crates/parcel_config/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod map; -pub mod parcel_config; -pub mod parcel_config_fixtures; -pub mod parcel_rc; -pub mod parcel_rc_config_loader; -mod partial_parcel_config; - -pub use parcel_config::ParcelConfig; -pub use parcel_config::PluginNode; diff --git a/crates/parcel_config/src/map.rs b/crates/parcel_config/src/map.rs deleted file mode 100644 index 3b7d30b0917..00000000000 --- a/crates/parcel_config/src/map.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod matcher; -use matcher::*; - -mod named_pipelines_map; -pub use named_pipelines_map::*; - -mod pipeline_map; -pub use pipeline_map::*; - -mod pipelines_map; -pub use pipelines_map::*; diff --git a/crates/parcel_config/src/map/matcher.rs b/crates/parcel_config/src/map/matcher.rs deleted file mode 100644 index 626109480bb..00000000000 --- a/crates/parcel_config/src/map/matcher.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::path::Path; - -use glob_match::glob_match; - -pub fn named_pattern_matcher<'a>(path: &'a Path) -> impl Fn(&str, &str) -> bool + 'a { - let basename = path.file_name().unwrap().to_str().unwrap(); - let path = path.as_os_str().to_str().unwrap(); - - |pattern, pipeline| { - let (named_pipeline, pattern) = pattern.split_once(':').unwrap_or(("", pattern)); - pipeline == named_pipeline && (glob_match(pattern, basename) || glob_match(pattern, path)) - } -} - -pub fn pattern_matcher<'a>(path: &'a Path) -> impl Fn(&str) -> bool + 'a { - let is_match = named_pattern_matcher(path); - - move |pattern| is_match(pattern, "") -} - -#[cfg(test)] -mod tests { - use std::env; - use std::path::PathBuf; - - use super::*; - - fn paths(filename: &str) -> Vec { - let cwd = env::current_dir().unwrap(); - vec![ - PathBuf::from(filename), - cwd.join(filename), - cwd.join("src").join(filename), - ] - } - - mod named_pattern_matcher { - use super::*; - - #[test] - fn returns_false_when_path_does_not_match_pattern_with_empty_pipeline() { - for path in paths("a.ts") { - let is_match = named_pattern_matcher(&path); - - assert_eq!(is_match("*.t", ""), false); - assert_eq!(is_match("*.tsx", ""), false); - assert_eq!(is_match("types:*.{ts,tsx}", ""), false); - assert_eq!(is_match("url:*", ""), false); - } - } - - #[test] - fn returns_false_when_path_does_not_match_pipeline() { - for path in paths("a.ts") { - let is_match = named_pattern_matcher(&path); - - assert_eq!(is_match("types:*.{ts,tsx}", "type"), false); - assert_eq!(is_match("types:*.{ts,tsx}", "typesa"), false); - } - } - - #[test] - fn returns_true_when_path_matches_pattern_with_empty_pipeline() { - for path in paths("a.ts") { - let is_match = named_pattern_matcher(&path); - - assert_eq!(is_match("*.{ts,tsx}", ""), true); - assert_eq!(is_match("*.ts", ""), true); - assert_eq!(is_match("*", ""), true); - } - } - - #[test] - fn returns_true_when_path_matches_pattern_and_pipeline() { - for path in paths("a.ts") { - let is_match = named_pattern_matcher(&path); - - assert_eq!(is_match("types:*.{ts,tsx}", "types"), true); - assert_eq!(is_match("types:*.ts", "types"), true); - assert_eq!(is_match("types:*", "types"), true); - } - } - } - - mod pattern_matcher { - use super::*; - - #[test] - fn returns_false_when_path_does_not_match_pattern() { - for path in paths("a.ts") { - let is_match = pattern_matcher(&path); - - assert_eq!(is_match("*.t"), false); - assert_eq!(is_match("*.tsx"), false); - assert_eq!(is_match("types:*.{ts,tsx}"), false); - assert_eq!(is_match("url:*"), false); - } - } - - #[test] - fn returns_true_when_path_matches_pattern_with_empty_pipeline() { - for path in paths("a.ts") { - let is_match = pattern_matcher(&path); - - assert_eq!(is_match("*.{ts,tsx}"), true); - assert_eq!(is_match("*.ts"), true); - assert_eq!(is_match("*"), true); - } - } - } -} diff --git a/crates/parcel_config/src/map/named_pipelines_map.rs b/crates/parcel_config/src/map/named_pipelines_map.rs deleted file mode 100644 index a92eff3b290..00000000000 --- a/crates/parcel_config/src/map/named_pipelines_map.rs +++ /dev/null @@ -1,427 +0,0 @@ -use std::hash::Hash; -use std::hash::Hasher; -use std::path::Path; - -use indexmap::IndexMap; -use serde::Deserialize; -use serde::Serialize; - -use super::named_pattern_matcher; -use crate::PluginNode; - -// -pub struct NamedPattern<'a> { - /// The name of the pipeline - /// - /// For example, this could be "js", "toml", "ts", etc - /// - pub pipeline: &'a str, - - /// Whether an unnamed pipeline pattern can be included in the result - pub use_fallback: bool, -} - -/// Represents fields in .parcelrc that map a pattern or named pattern to a list of plugin names -/// -/// # Examples -/// -/// ``` -/// use std::path::PathBuf; -/// use std::sync::Arc; -/// -/// use indexmap::indexmap; -/// use parcel_config::map::NamedPipelinesMap; -/// use parcel_config::PluginNode; -/// -/// NamedPipelinesMap::new(indexmap! { -/// String::from("*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}") => vec![PluginNode { -/// package_name: String::from("@parcel/transformer-js"), -/// resolve_from: Arc::new(PathBuf::default()), -/// }] -/// }); -/// ``` -/// -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(transparent)] -pub struct NamedPipelinesMap { - /// Maps patterns and named patterns to a series of plugins, called pipelines - inner: IndexMap>, -} - -impl Hash for NamedPipelinesMap { - fn hash(&self, state: &mut H) { - for item in self.inner.iter() { - item.hash(state); - } - } -} - -impl NamedPipelinesMap { - pub fn new(map: IndexMap>) -> Self { - Self { inner: map } - } - - /// Finds pipelines contained by a pattern that match the given file path and named pipeline - /// - /// This function will return an empty vector when a pipeline is provided and there are no exact - /// matches. Otherwise, exact pattern matches will be returned followed by any other matching - /// patterns. - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use std::path::PathBuf; - /// use std::sync::Arc; - /// - /// use indexmap::indexmap; - /// use parcel_config::map::NamedPattern; - /// use parcel_config::map::NamedPipelinesMap; - /// use parcel_config::PluginNode; - /// - /// let pipelines_map = NamedPipelinesMap::new(indexmap! { - /// String::from("types:*.{ts,tsx}") => vec![PluginNode { - /// package_name: String::from("@parcel/transformer-typescript-types"), - /// resolve_from: Arc::new(PathBuf::default()), - /// }], - /// String::from("*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}") => vec![PluginNode { - /// package_name: String::from("@parcel/transformer-js"), - /// resolve_from: Arc::new(PathBuf::default()), - /// }], - /// }); - /// - /// pipelines_map.get(Path::new("component.tsx"), None); - /// - /// pipelines_map.get( - /// Path::new("component.tsx"), - /// Some(NamedPattern { - /// pipeline: "types", - /// use_fallback: false, - /// }) - /// ); - /// - /// pipelines_map.get( - /// Path::new("component.tsx"), - /// Some(NamedPattern { - /// pipeline: "types", - /// use_fallback: true, - /// }) - /// ); - /// ``` - pub fn get(&self, path: &Path, named_pattern: Option) -> Vec { - let is_match = named_pattern_matcher(path); - let mut matches: Vec = Vec::new(); - - // If a named pipeline is requested, the glob needs to match exactly - if let Some(named_pattern) = named_pattern { - let exact_match = self - .inner - .iter() - .find(|(pattern, _)| is_match(pattern, named_pattern.pipeline.as_ref())); - - if let Some((_, pipelines)) = exact_match { - matches.extend(pipelines.iter().cloned()); - } else if !named_pattern.use_fallback { - return Vec::new(); - } - } - - for (pattern, pipelines) in self.inner.iter() { - if is_match(&pattern, "") { - matches.extend(pipelines.iter().cloned()); - } - } - - matches - } - - pub fn contains_named_pipeline(&self, pipeline: impl AsRef) -> bool { - let named_pipeline = format!("{}:", pipeline.as_ref()); - - self - .inner - .keys() - .any(|glob| glob.starts_with(&named_pipeline)) - } - - pub fn named_pipelines(&self) -> Vec { - self - .inner - .keys() - .filter_map(|glob| { - glob - .split_once(':') - .map(|(named_pipeline, _pattern)| String::from(named_pipeline)) - }) - .collect() - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - use std::sync::Arc; - - use super::*; - - fn pipelines(name: &str) -> Vec { - vec![PluginNode { - package_name: format!("@parcel/plugin-{}", name), - resolve_from: Arc::new(PathBuf::default()), - }] - } - - mod get { - use indexmap::indexmap; - - use super::*; - - #[test] - fn returns_empty_vec_for_empty_map() { - let empty_map = NamedPipelinesMap::default(); - - assert_eq!(empty_map.get(Path::new("a.js"), None), Vec::new()); - assert_eq!(empty_map.get(Path::new("a.toml"), None), Vec::new()); - } - - #[test] - fn returns_empty_vec_when_no_matching_path() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2") - }); - - assert_eq!(map.get(Path::new("a.css"), None), Vec::new()); - assert_eq!(map.get(Path::new("a.jsx"), None), Vec::new()); - assert_eq!(map.get(Path::new("a.tsx"), None), Vec::new()); - assert_eq!(map.get(Path::new("a.tom"), None), Vec::new()); - assert_eq!(map.get(Path::new("a.tomla"), None), Vec::new()); - } - - #[test] - fn returns_empty_vec_when_no_matching_pipeline_without_fallback() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2"), - String::from("types:*.{ts,tsx}") => pipelines("3"), - String::from("url:*") => pipelines("4") - }); - - let assert_empty_vec = |path: &str, pipeline: &str| { - assert_eq!( - map.get( - Path::new(path), - Some(NamedPattern { - pipeline, - use_fallback: false - }) - ), - Vec::new() - ); - }; - - assert_empty_vec("a.css", "css"); - - assert_empty_vec("a.js", "data-url"); - assert_empty_vec("a.js", "urla"); - - assert_empty_vec("a.toml", "toml"); - - assert_empty_vec("a.ts", "typesa"); - assert_empty_vec("a.tsx", "typesa"); - } - - #[test] - fn returns_empty_vec_when_no_matching_pipeline_with_fallback() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("types:*.{ts,tsx}") => pipelines("3"), - }); - - let assert_empty_vec = |path: &str, pipeline: &str| { - assert_eq!( - map.get( - Path::new(path), - Some(NamedPattern { - pipeline, - use_fallback: true - }) - ), - Vec::new() - ); - }; - - assert_empty_vec("a.css", "css"); - assert_empty_vec("a.jsx", "typesa"); - assert_empty_vec("a.tsx", "typesa"); - } - - #[test] - fn returns_matching_plugins_for_empty_pipeline() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2") - }); - - assert_eq!(map.get(Path::new("a.js"), None), pipelines("1")); - assert_eq!(map.get(Path::new("a.ts"), None), pipelines("1")); - assert_eq!(map.get(Path::new("a.toml"), None), pipelines("2")); - } - - #[test] - fn returns_matching_plugins_for_pipeline_without_fallback() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("types:*.{ts,tsx}") => pipelines("2"), - String::from("url:*") => pipelines("3") - }); - - let assert_plugins = |path: &str, pipeline: &str, plugins: Vec| { - assert_eq!( - map.get( - Path::new(path), - Some(NamedPattern { - pipeline, - use_fallback: false - }) - ), - plugins - ); - }; - - assert_plugins("a.ts", "types", [pipelines("2"), pipelines("1")].concat()); - assert_plugins("a.tsx", "types", pipelines("2")); - - assert_plugins("a.js", "url", [pipelines("3"), pipelines("1")].concat()); - assert_plugins("a.url", "url", pipelines("3")); - } - - #[test] - fn returns_matching_plugins_for_pipeline_with_fallback() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("types:*.{ts,tsx}") => pipelines("2"), - String::from("url:*") => pipelines("3") - }); - - let assert_plugins = |path: &str, pipeline: &str, plugins: Vec| { - assert_eq!( - map.get( - Path::new(path), - Some(NamedPattern { - pipeline, - use_fallback: true - }) - ), - plugins - ); - }; - - assert_plugins("a.ts", "types", [pipelines("2"), pipelines("1")].concat()); - assert_plugins("a.tsx", "types", pipelines("2")); - assert_plugins("a.ts", "typesa", pipelines("1")); - - assert_plugins("a.url", "url", pipelines("3")); - assert_plugins("a.js", "url", [pipelines("3"), pipelines("1")].concat()); - assert_plugins("a.js", "urla", pipelines("1")); - } - } - - mod contains_named_pipeline { - use indexmap::indexmap; - - use super::*; - - #[test] - fn returns_true_when_named_pipeline_exists() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("data-url:*") => pipelines("1") - }); - - assert_eq!(map.contains_named_pipeline("data-url"), true); - } - - #[test] - fn returns_false_for_empty_map() { - let empty_map = NamedPipelinesMap::default(); - - assert_eq!(empty_map.contains_named_pipeline("data-url"), false); - assert_eq!(empty_map.contains_named_pipeline("types"), false); - } - - #[test] - fn returns_false_when_named_pipeline_does_not_exist() { - let map = NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2"), - String::from("url:*") => pipelines("3") - }); - - assert_eq!(map.contains_named_pipeline("*"), false); - assert_eq!(map.contains_named_pipeline("data-url"), false); - assert_eq!(map.contains_named_pipeline("types"), false); - assert_eq!(map.contains_named_pipeline("urls"), false); - } - } - - mod named_pipelines { - use indexmap::indexmap; - - use super::*; - - #[test] - fn returns_empty_vec_when_no_named_pipelines() { - let empty_vec: Vec<&str> = Vec::new(); - - assert_eq!(NamedPipelinesMap::default().named_pipelines(), empty_vec); - assert_eq!( - NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2"), - }) - .named_pipelines(), - empty_vec, - ); - } - - #[test] - fn returns_list_of_named_pipelines() { - assert_eq!( - NamedPipelinesMap::new(indexmap! { - String::from("data-url:*") => pipelines("1") - }) - .named_pipelines(), - vec!("data-url") - ); - - assert_eq!( - NamedPipelinesMap::new(indexmap! { - String::from("types:*.{ts,tsx}") => pipelines("1") - }) - .named_pipelines(), - vec!("types") - ); - - assert_eq!( - NamedPipelinesMap::new(indexmap! { - String::from("url:*") => pipelines("1") - }) - .named_pipelines(), - vec!("url") - ); - - assert_eq!( - NamedPipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.toml") => pipelines("2"), - String::from("bundle-text:*") => pipelines("3"), - String::from("data-url:*") => pipelines("4"), - String::from("types:*.{ts,tsx}") => pipelines("5"), - String::from("url:*") => pipelines("6") - }) - .named_pipelines(), - vec!("bundle-text", "data-url", "types", "url") - ); - } - } -} diff --git a/crates/parcel_config/src/map/pipeline_map.rs b/crates/parcel_config/src/map/pipeline_map.rs deleted file mode 100644 index b2ad6767d11..00000000000 --- a/crates/parcel_config/src/map/pipeline_map.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::path::Path; - -use indexmap::IndexMap; -use serde::Deserialize; -use serde::Serialize; - -use super::pattern_matcher; -use crate::PluginNode; - -/// Represents fields in .parcelrc that map a pattern to a single plugin name -/// -/// # Examples -/// -/// ``` -/// use std::path::PathBuf; -/// use std::sync::Arc; -/// -/// use indexmap::indexmap; -/// use parcel_config::map::PipelineMap; -/// use parcel_config::PluginNode; -/// -/// PipelineMap::new(indexmap! { -/// String::from("*.{js,mjs,cjs}") => PluginNode { -/// package_name: String::from("@parcel/packager-js"), -/// resolve_from: Arc::new(PathBuf::default()), -/// } -/// }); -/// ``` -/// -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct PipelineMap( - /// Maps patterns to a single pipeline plugin - IndexMap, -); - -impl PipelineMap { - pub fn new(map: IndexMap) -> Self { - Self(map) - } - - /// Finds the plugin that matches the given file path - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use std::path::PathBuf; - /// use std::sync::Arc; - /// - /// use indexmap::indexmap; - /// use parcel_config::map::PipelineMap; - /// use parcel_config::PluginNode; - /// - /// let pipeline_map = PipelineMap::new(indexmap! { - /// String::from("*.{js,mjs,cjs}") => PluginNode { - /// package_name: String::from("@parcel/packager-js"), - /// resolve_from: Arc::new(PathBuf::default()), - /// } - /// }); - /// - /// pipeline_map.get(Path::new("component.js")); - /// pipeline_map.get(Path::new("Cargo.toml")); - /// ``` - pub fn get(&self, path: &Path) -> Option<&PluginNode> { - let is_match = pattern_matcher(path); - - for (pattern, plugin) in self.0.iter() { - if is_match(pattern) { - return Some(plugin); - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - use std::sync::Arc; - - use super::*; - - mod get { - use indexmap::indexmap; - - use super::*; - - fn pipeline(name: &str) -> PluginNode { - PluginNode { - package_name: format!("@parcel/plugin-{}", name), - resolve_from: Arc::new(PathBuf::default()), - } - } - - #[test] - fn returns_none_for_empty_map() { - let empty_map = PipelineMap::default(); - - assert_eq!(empty_map.get(Path::new("a.js")), None); - assert_eq!(empty_map.get(Path::new("a.toml")), None); - } - - #[test] - fn returns_none_when_no_matching_path() { - let map = PipelineMap::new(indexmap! { - String::from("*.{js,ts}") => pipeline("1"), - }); - - assert_eq!(map.get(Path::new("a.css")), None); - assert_eq!(map.get(Path::new("a.jsx")), None); - assert_eq!(map.get(Path::new("a.tsx")), None); - } - - #[test] - fn returns_first_matching_pipeline() { - let map = PipelineMap::new(indexmap! { - String::from("*.{js,ts}") => pipeline("1"), - String::from("*.ts") => pipeline("2"), - String::from("*.toml") => pipeline("3") - }); - - assert_eq!(map.get(Path::new("a.js")), Some(&pipeline("1"))); - assert_eq!(map.get(Path::new("a.ts")), Some(&pipeline("1"))); - assert_eq!(map.get(Path::new("a.toml")), Some(&pipeline("3"))); - } - } -} diff --git a/crates/parcel_config/src/map/pipelines_map.rs b/crates/parcel_config/src/map/pipelines_map.rs deleted file mode 100644 index 9f6895ef894..00000000000 --- a/crates/parcel_config/src/map/pipelines_map.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::path::Path; - -use indexmap::IndexMap; -use serde::Deserialize; -use serde::Serialize; - -use super::pattern_matcher; -use crate::PluginNode; - -/// Represents fields in .parcelrc that map a pattern to a list of plugin names -/// -/// # Examples -/// -/// ``` -/// use std::path::PathBuf; -/// use std::sync::Arc; -/// -/// use indexmap::indexmap; -/// use parcel_config::map::PipelinesMap; -/// use parcel_config::PluginNode; -/// -/// PipelinesMap::new(indexmap! { -/// String::from("*") => vec![PluginNode { -/// package_name: String::from("@parcel/compressor-raw"), -/// resolve_from: Arc::new(PathBuf::default()), -/// }] -/// }); -/// ``` -/// -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct PipelinesMap( - /// Maps patterns to a series of plugins, called pipelines - IndexMap>, -); - -impl PipelinesMap { - pub fn new(map: IndexMap>) -> Self { - Self(map) - } - - /// Finds pipelines that match the given file path - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use std::path::PathBuf; - /// use std::sync::Arc; - /// - /// use indexmap::indexmap; - /// use parcel_config::map::PipelinesMap; - /// use parcel_config::PluginNode; - /// - /// let pipelines_map = PipelinesMap::new(indexmap! { - /// String::from("*") => vec![PluginNode { - /// package_name: String::from("@parcel/compressor-raw"), - /// resolve_from: Arc::new(PathBuf::default()), - /// }] - /// }); - /// - /// pipelines_map.get(Path::new("component.tsx")); - /// pipelines_map.get(Path::new("Cargo.toml")); - /// ``` - pub fn get(&self, path: &Path) -> Vec { - let is_match = pattern_matcher(path); - let mut matches: Vec = Vec::new(); - - for (pattern, pipelines) in self.0.iter() { - if is_match(&pattern) { - matches.extend(pipelines.iter().cloned()); - } - } - - matches - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - use std::sync::Arc; - - use super::*; - - fn pipelines(name: &str) -> Vec { - vec![PluginNode { - package_name: format!("@parcel/plugin-{}", name), - resolve_from: Arc::new(PathBuf::default()), - }] - } - - mod get { - use indexmap::indexmap; - - use super::*; - - #[test] - fn returns_empty_vec_for_empty_map() { - let empty_map = PipelinesMap::default(); - - assert_eq!(empty_map.get(Path::new("a.js")), Vec::new()); - assert_eq!(empty_map.get(Path::new("a.toml")), Vec::new()); - } - - #[test] - fn returns_empty_vec_when_no_matching_path() { - let map = PipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - }); - - assert_eq!(map.get(Path::new("a.css")), Vec::new()); - assert_eq!(map.get(Path::new("a.jsx")), Vec::new()); - assert_eq!(map.get(Path::new("a.tsx")), Vec::new()); - } - - #[test] - fn returns_matching_plugins_for_path() { - let map = PipelinesMap::new(indexmap! { - String::from("*.{js,ts}") => pipelines("1"), - String::from("*.ts") => pipelines("2"), - String::from("*.toml") => pipelines("3"), - }); - - assert_eq!(map.get(Path::new("a.js")), pipelines("1")); - assert_eq!( - map.get(Path::new("a.ts")), - [pipelines("1"), pipelines("2")].concat() - ); - assert_eq!(map.get(Path::new("a.toml")), pipelines("3")); - } - } -} diff --git a/crates/parcel_config/src/parcel_config.rs b/crates/parcel_config/src/parcel_config.rs deleted file mode 100644 index dd4fd570dbc..00000000000 --- a/crates/parcel_config/src/parcel_config.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use indexmap::IndexMap; -use parcel_core::diagnostic_error; -use parcel_core::types::DiagnosticError; -use serde::Deserialize; -use serde::Serialize; - -use super::partial_parcel_config::PartialParcelConfig; -use crate::map::NamedPipelinesMap; -use crate::map::PipelineMap; -use crate::map::PipelinesMap; - -#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PluginNode { - pub package_name: String, - pub resolve_from: Arc, -} - -/// Represents a fully merged and validated .parcel_rc config -#[derive(Debug, Deserialize, PartialEq, Serialize)] -pub struct ParcelConfig { - pub bundler: PluginNode, - pub compressors: PipelinesMap, - pub namers: Vec, - pub optimizers: NamedPipelinesMap, - pub packagers: PipelineMap, - pub reporters: Vec, - pub resolvers: Vec, - pub runtimes: Vec, - pub transformers: NamedPipelinesMap, - pub validators: PipelinesMap, -} - -impl TryFrom for ParcelConfig { - type Error = DiagnosticError; - - fn try_from(config: PartialParcelConfig) -> Result { - // The final stage of merging filters out any ... extensions as they are a noop - fn filter_out_extends(pipelines: Vec) -> Vec { - pipelines - .into_iter() - .filter(|p| p.package_name != "...") - .collect() - } - - fn filter_out_extends_from_map( - map: IndexMap>, - ) -> IndexMap> { - map - .into_iter() - .map(|(pattern, plugins)| (pattern, filter_out_extends(plugins))) - .collect() - } - - let mut missing_phases = Vec::new(); - - if let None = config.bundler { - missing_phases.push(String::from("bundler")); - } - - let namers = filter_out_extends(config.namers); - if namers.is_empty() { - missing_phases.push(String::from("namers")); - } - - let resolvers = filter_out_extends(config.resolvers); - if resolvers.is_empty() { - missing_phases.push(String::from("resolvers")); - } - - if !missing_phases.is_empty() { - return Err(diagnostic_error!( - "Missing plugins for the following phases: {:?}", - missing_phases - )); - } - - Ok(ParcelConfig { - bundler: config.bundler.unwrap(), - compressors: PipelinesMap::new(filter_out_extends_from_map(config.compressors)), - namers, - optimizers: NamedPipelinesMap::new(filter_out_extends_from_map(config.optimizers)), - packagers: PipelineMap::new(config.packagers), - reporters: filter_out_extends(config.reporters), - resolvers, - runtimes: filter_out_extends(config.runtimes), - transformers: NamedPipelinesMap::new(filter_out_extends_from_map(config.transformers)), - validators: PipelinesMap::new(filter_out_extends_from_map(config.validators)), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - mod try_from { - use super::*; - use crate::partial_parcel_config::PartialParcelConfigBuilder; - - #[test] - fn returns_an_error_when_required_phases_are_optional() { - assert_eq!( - ParcelConfig::try_from(PartialParcelConfig::default()).map_err(|e| e.to_string()), - Err( - diagnostic_error!( - "Missing plugins for the following phases: {:?}", - vec!("bundler", "namers", "resolvers") - ) - .to_string() - ) - ); - } - - #[test] - fn returns_the_config() { - fn plugin() -> PluginNode { - PluginNode { - package_name: String::from("package"), - resolve_from: Arc::new(PathBuf::from("/")), - } - } - - fn extension() -> PluginNode { - PluginNode { - package_name: String::from("..."), - resolve_from: Arc::new(PathBuf::from("/")), - } - } - - let partial_config = PartialParcelConfigBuilder::default() - .bundler(Some(plugin())) - .namers(vec![plugin()]) - .resolvers(vec![extension(), plugin()]) - .build() - .unwrap(); - - let config = ParcelConfig::try_from(partial_config); - - assert!(config.is_ok_and(|c| !c.resolvers.contains(&extension()))); - } - } -} diff --git a/crates/parcel_config/src/parcel_config_fixtures.rs b/crates/parcel_config/src/parcel_config_fixtures.rs deleted file mode 100644 index db342326406..00000000000 --- a/crates/parcel_config/src/parcel_config_fixtures.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; - -use indexmap::indexmap; -use indexmap::IndexMap; - -use super::map::NamedPipelinesMap; -use super::parcel_config::ParcelConfig; -use crate::map::PipelineMap; -use crate::map::PipelinesMap; -use crate::parcel_config::PluginNode; - -pub struct ConfigFixture { - pub parcel_config: ParcelConfig, - pub parcel_rc: String, - pub path: PathBuf, -} - -pub struct PartialConfigFixture { - pub parcel_rc: String, - pub path: PathBuf, -} - -pub struct ExtendedConfigFixture { - pub base_config: PartialConfigFixture, - pub extended_config: PartialConfigFixture, - pub parcel_config: ParcelConfig, -} - -pub fn config(project_root: &Path) -> (String, ConfigFixture) { - ( - String::from("@config/default"), - default_config(Arc::new( - project_root - .join("node_modules") - .join("@config/default") - .join("index.json"), - )), - ) -} - -pub fn fallback_config(project_root: &Path) -> (String, ConfigFixture) { - ( - String::from("@parcel/config-default"), - default_config(Arc::new( - project_root - .join("node_modules") - .join("@parcel/config-default") - .join("index.json"), - )), - ) -} - -pub fn default_config(resolve_from: Arc) -> ConfigFixture { - ConfigFixture { - parcel_config: ParcelConfig { - bundler: PluginNode { - package_name: String::from("@parcel/bundler-default"), - resolve_from: resolve_from.clone(), - }, - compressors: PipelinesMap::new(indexmap! { - String::from("*") => vec!(PluginNode { - package_name: String::from("@parcel/compressor-raw"), - resolve_from: resolve_from.clone(), - }) - }), - namers: vec![PluginNode { - package_name: String::from("@parcel/namer-default"), - resolve_from: resolve_from.clone(), - }], - optimizers: NamedPipelinesMap::new(indexmap! { - String::from("*.{js,mjs,cjs}") => vec!(PluginNode { - package_name: String::from("@parcel/optimizer-swc"), - resolve_from: resolve_from.clone(), - }) - }), - packagers: PipelineMap::new(indexmap! { - String::from("*.{js,mjs,cjs}") => PluginNode { - package_name: String::from("@parcel/packager-js"), - resolve_from: resolve_from.clone(), - } - }), - reporters: vec![PluginNode { - package_name: String::from("@parcel/reporter-dev-server"), - resolve_from: resolve_from.clone(), - }], - resolvers: vec![PluginNode { - package_name: String::from("@parcel/resolver-default"), - resolve_from: resolve_from.clone(), - }], - runtimes: vec![PluginNode { - package_name: String::from("@parcel/runtime-js"), - resolve_from: resolve_from.clone(), - }], - transformers: NamedPipelinesMap::new(indexmap! { - String::from("*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}") => vec!(PluginNode { - package_name: String::from("@parcel/transformer-js"), - resolve_from: resolve_from.clone(), - }) - }), - validators: PipelinesMap::new(IndexMap::new()), - }, - parcel_rc: String::from( - r#" - { - "bundler": "@parcel/bundler-default", - "compressors": { - "*": ["@parcel/compressor-raw"] - }, - "namers": ["@parcel/namer-default"], - "optimizers": { - "*.{js,mjs,cjs}": ["@parcel/optimizer-swc"] - }, - "packagers": { - "*.{js,mjs,cjs}": "@parcel/packager-js" - }, - "reporters": ["@parcel/reporter-dev-server"], - "resolvers": ["@parcel/resolver-default"], - "runtimes": ["@parcel/runtime-js"], - "transformers": { - "*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": [ - "@parcel/transformer-js" - ], - } - } - "#, - ), - path: PathBuf::from(resolve_from.display().to_string()), - } -} - -fn extended_config_from( - project_root: &Path, - base_resolve_from: Arc, -) -> ExtendedConfigFixture { - let extended_resolve_from = Arc::new( - project_root - .join("node_modules") - .join("@parcel/config-default") - .join("index.json"), - ); - - let extended_config = default_config(extended_resolve_from.clone()); - - ExtendedConfigFixture { - parcel_config: ParcelConfig { - bundler: PluginNode { - package_name: String::from("@parcel/bundler-default"), - resolve_from: extended_resolve_from.clone(), - }, - compressors: PipelinesMap::new(indexmap! { - String::from("*") => vec!(PluginNode { - package_name: String::from("@parcel/compressor-raw"), - resolve_from: extended_resolve_from.clone(), - }) - }), - namers: vec![PluginNode { - package_name: String::from("@parcel/namer-default"), - resolve_from: extended_resolve_from.clone(), - }], - optimizers: NamedPipelinesMap::new(indexmap! { - String::from("*.{js,mjs,cjs}") => vec!(PluginNode { - package_name: String::from("@parcel/optimizer-swc"), - resolve_from: extended_resolve_from.clone(), - }) - }), - packagers: PipelineMap::new(indexmap! { - String::from("*.{js,mjs,cjs}") => PluginNode { - package_name: String::from("@parcel/packager-js"), - resolve_from: extended_resolve_from.clone(), - } - }), - reporters: vec![ - PluginNode { - package_name: String::from("@parcel/reporter-dev-server"), - resolve_from: extended_resolve_from.clone(), - }, - PluginNode { - package_name: String::from("@scope/parcel-metrics-reporter"), - resolve_from: base_resolve_from.clone(), - }, - ], - resolvers: vec![PluginNode { - package_name: String::from("@parcel/resolver-default"), - resolve_from: extended_resolve_from.clone(), - }], - runtimes: vec![PluginNode { - package_name: String::from("@parcel/runtime-js"), - resolve_from: extended_resolve_from.clone(), - }], - transformers: NamedPipelinesMap::new(indexmap! { - String::from("*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}") => vec!(PluginNode { - package_name: String::from("@parcel/transformer-js"), - resolve_from: extended_resolve_from.clone(), - }), - String::from("*.{ts,tsx}") => vec!(PluginNode { - package_name: String::from("@scope/parcel-transformer-ts"), - resolve_from: base_resolve_from.clone(), - }), - }), - validators: PipelinesMap::new(IndexMap::new()), - }, - base_config: PartialConfigFixture { - path: PathBuf::from(base_resolve_from.as_os_str()), - parcel_rc: String::from( - r#" - { - "extends": "@parcel/config-default", - "reporters": ["...", "@scope/parcel-metrics-reporter"], - "transformers": { - "*.{ts,tsx}": [ - "@scope/parcel-transformer-ts", - "..." - ] - } - } - "#, - ), - }, - extended_config: PartialConfigFixture { - path: extended_config.path, - parcel_rc: extended_config.parcel_rc, - }, - } -} - -pub fn default_extended_config(project_root: &Path) -> ExtendedConfigFixture { - let base_resolve_from = Arc::from(project_root.join(".parcelrc")); - - extended_config_from(project_root, base_resolve_from) -} - -pub fn extended_config(project_root: &Path) -> (String, ExtendedConfigFixture) { - let base_resolve_from = Arc::from( - project_root - .join("node_modules") - .join("@config/default") - .join("index.json"), - ); - - ( - String::from("@config/default"), - extended_config_from(project_root, base_resolve_from), - ) -} diff --git a/crates/parcel_config/src/parcel_rc.rs b/crates/parcel_config/src/parcel_rc.rs deleted file mode 100644 index 21b0f65d154..00000000000 --- a/crates/parcel_config/src/parcel_rc.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; - -use indexmap::IndexMap; -use parcel_core::types::File; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -#[serde(untagged)] -pub enum Extends { - One(String), - Many(Vec), -} - -/// Deserialized .parcel_rc config -#[derive(Debug, Deserialize)] -pub struct ParcelRc { - pub extends: Option, - pub bundler: Option, - pub compressors: Option>>, - pub namers: Option>, - pub optimizers: Option>>, - pub packagers: Option>, - pub reporters: Option>, - pub resolvers: Option>, - pub runtimes: Option>, - pub transformers: Option>>, - pub validators: Option>>, -} - -/// Represents the .parcel_rc config file -#[derive(Debug)] -pub struct ParcelRcFile { - pub contents: ParcelRc, - pub path: PathBuf, - pub raw: String, -} - -impl From<&ParcelRcFile> for File { - fn from(parcel_rc: &ParcelRcFile) -> Self { - File { - contents: parcel_rc.raw.clone(), - path: parcel_rc.path.clone(), - } - } -} diff --git a/crates/parcel_config/src/parcel_rc_config_loader.rs b/crates/parcel_config/src/parcel_rc_config_loader.rs deleted file mode 100644 index fb733b246c1..00000000000 --- a/crates/parcel_config/src/parcel_rc_config_loader.rs +++ /dev/null @@ -1,852 +0,0 @@ -use std::path::Path; -use std::path::PathBuf; - -use parcel_core::diagnostic_error; -use parcel_core::types::CodeFrame; -use parcel_core::types::CodeHighlight; -use parcel_core::types::DiagnosticBuilder; -use parcel_core::types::DiagnosticError; -use parcel_core::types::File; -use parcel_filesystem::search::find_ancestor_file; -use parcel_filesystem::FileSystemRef; -use parcel_package_manager::PackageManagerRef; -use pathdiff::diff_paths; -use serde_json5::Location; - -use super::parcel_config::ParcelConfig; -use super::parcel_config::PluginNode; -use super::parcel_rc::Extends; -use super::parcel_rc::ParcelRcFile; -use super::partial_parcel_config::PartialParcelConfig; - -#[derive(Default)] -pub struct LoadConfigOptions<'a> { - /// A list of additional reporter plugins that will be appended to the reporters config - pub additional_reporters: Vec, - /// A file path or package specifier that will be used to load the config from - pub config: Option<&'a str>, - /// A file path or package specifier that will be used to load the config from when no other - /// .parcelrc can be found - pub fallback_config: Option<&'a str>, -} - -/// Loads and validates .parcel_rc config -pub struct ParcelRcConfigLoader { - fs: FileSystemRef, - package_manager: PackageManagerRef, -} - -impl ParcelRcConfigLoader { - pub fn new(fs: FileSystemRef, package_manager: PackageManagerRef) -> Self { - ParcelRcConfigLoader { - fs, - package_manager, - } - } - - fn find_config(&self, project_root: &Path, path: &Path) -> Result { - let from = path.parent().unwrap_or(path); - - find_ancestor_file(&*self.fs, &[".parcelrc"], from, project_root) - .ok_or_else(|| diagnostic_error!("Unable to locate .parcelrc from {}", from.display())) - } - - fn resolve_from(&self, project_root: &Path) -> PathBuf { - let cwd = self.fs.cwd().unwrap(); - let relative = diff_paths(cwd.clone(), project_root); - let is_cwd_inside_project_root = - relative.is_some_and(|p| !p.starts_with("..") && !p.is_absolute()); - - let dir = if is_cwd_inside_project_root { - &cwd - } else { - project_root - }; - - dir.join("index") - } - - fn load_config( - &self, - path: PathBuf, - ) -> Result<(PartialParcelConfig, Vec), DiagnosticError> { - let raw = self.fs.read_to_string(&path).map_err(|source| { - diagnostic_error!(DiagnosticBuilder::default() - .message(source.to_string()) - .code_frames(vec![CodeFrame::from(path.clone())])) - })?; - - let contents = serde_json5::from_str(&raw).map_err(|error| { - serde_to_diagnostic_error( - error, - File { - contents: raw.clone(), - path: path.clone(), - }, - ) - })?; - - self.process_config(ParcelRcFile { - contents, - path, - raw, - }) - } - - fn resolve_extends( - &self, - parcel_rc_file: &ParcelRcFile, - extend: &str, - ) -> Result { - let path = if extend.starts_with(".") { - parcel_rc_file - .path - .parent() - .unwrap_or(&parcel_rc_file.path) - .join(extend) - } else { - self - .package_manager - .resolve(extend, &parcel_rc_file.path) - .map_err(|source| { - source.context(diagnostic_error!(DiagnosticBuilder::default() - .message(format!( - "Failed to resolve extended config {extend} from {}", - parcel_rc_file.path.display() - )) - .code_frames(vec![CodeFrame::from(File::from(parcel_rc_file))]))) - })? - .resolved - }; - - self.fs.canonicalize_base(&path).map_err(|source| { - diagnostic_error!("{}", source).context(diagnostic_error!(DiagnosticBuilder::default() - .message(format!( - "Failed to resolve extended config {extend} from {}", - parcel_rc_file.path.display() - )) - .code_frames(vec![CodeFrame::from(File::from(parcel_rc_file))]))) - }) - } - - /// Processes a .parcelrc file by loading and merging "extends" configurations into a single - /// PartialParcelConfig struct - /// - /// Configuration merging will be applied to all "extends" configurations, before being merged - /// into the base config for a more natural merging order. It will replace any "..." seen in - /// plugin pipelines with the corresponding plugins from "extends" if present. - /// - fn process_config( - &self, - parcel_rc_file: ParcelRcFile, - ) -> Result<(PartialParcelConfig, Vec), DiagnosticError> { - let mut files = vec![parcel_rc_file.path.clone()]; - let extends = parcel_rc_file.contents.extends.as_ref(); - let extends = match extends { - None => Vec::new(), - Some(extends) => match extends { - Extends::One(ext) => vec![String::from(ext)], - Extends::Many(ext) => ext.to_vec(), - }, - }; - - if extends.is_empty() { - return Ok((PartialParcelConfig::try_from(parcel_rc_file)?, files)); - } - - let mut merged_config: Option = None; - for extend in extends { - let extended_file_path = self.resolve_extends(&parcel_rc_file, &extend)?; - let (extended_config, mut extended_file_paths) = self.load_config(extended_file_path)?; - - merged_config = match merged_config { - None => Some(extended_config), - Some(config) => Some(PartialParcelConfig::merge(config, extended_config)), - }; - - files.append(&mut extended_file_paths); - } - - let config = PartialParcelConfig::merge( - PartialParcelConfig::try_from(parcel_rc_file)?, - merged_config.unwrap(), - ); - - Ok((config, files)) - } - - /// Finds and loads a .parcelrc file - /// - /// By default the nearest .parcelrc ancestor file from the current working directory will be - /// loaded, unless the config or fallback_config option are specified. In cases where the - /// current working directory does not live within the project root, the default config will be - /// loaded from the project root. - /// - pub fn load( - &self, - project_root: &Path, - options: LoadConfigOptions, - ) -> Result<(ParcelConfig, Vec), DiagnosticError> { - let resolve_from = self.resolve_from(project_root); - let mut config_path = match options.config { - Some(config) => self - .package_manager - .resolve(&config, &resolve_from) - .map(|r| r.resolved) - .map_err(|source| { - source.context(diagnostic_error!( - "Failed to resolve config {config} from {}", - resolve_from.display() - )) - }), - None => self.find_config(project_root, &resolve_from), - }; - - if !config_path.is_ok() { - if let Some(fallback_config) = options.fallback_config { - config_path = self - .package_manager - .resolve(&fallback_config, &resolve_from) - .map(|r| r.resolved) - .map_err(|source| { - source.context(diagnostic_error!( - "Failed to resolve fallback {fallback_config} from {}", - resolve_from.display() - )) - }) - } - } - - let config_path = config_path?; - let (mut parcel_config, files) = self.load_config(config_path)?; - - if options.additional_reporters.len() > 0 { - parcel_config.reporters.extend(options.additional_reporters); - } - - let parcel_config = ParcelConfig::try_from(parcel_config)?; - - Ok((parcel_config, files)) - } -} - -fn serde_to_diagnostic_error(error: serde_json5::Error, file: File) -> DiagnosticError { - let mut diagnostic_error = DiagnosticBuilder::default(); - diagnostic_error.message(format!("Failed to parse {}", file.path.display())); - - match error { - serde_json5::Error::Message { msg, location } => { - let location = location.unwrap_or_else(|| Location { column: 1, line: 1 }); - - diagnostic_error.code_frames(vec![CodeFrame { - code_highlights: vec![CodeHighlight { - message: Some(msg), - ..CodeHighlight::from([location.line, location.column]) - }], - ..CodeFrame::from(file) - }]); - } - }; - - diagnostic_error!(diagnostic_error) -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use anyhow::anyhow; - use mockall::predicate::eq; - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - use parcel_filesystem::FileSystem; - use parcel_package_manager::MockPackageManager; - use parcel_package_manager::PackageManager; - use parcel_package_manager::Resolution; - - use super::*; - - fn fail_package_manager_resolution(package_manager: &mut MockPackageManager) { - package_manager - .expect_resolve() - .return_once(|_specifier, _from| Err(anyhow!("Something bad happened"))); - } - - struct TestPackageManager { - fs: FileSystemRef, - } - - impl PackageManager for TestPackageManager { - fn resolve(&self, specifier: &str, from: &Path) -> anyhow::Result { - let path = match "true" { - _s if specifier.starts_with(".") => from.join(specifier), - _s if specifier.starts_with("@") => self - .fs - .cwd() - .unwrap() - .join("node_modules") - .join(specifier) - .join("index.json"), - _ => PathBuf::from("Not found"), - }; - - if !self.fs.is_file(&path) { - return Err(anyhow!("File was missing")); - } - - Ok(Resolution { resolved: path }) - } - } - - fn package_manager_resolution( - package_manager: &mut MockPackageManager, - specifier: String, - from: PathBuf, - ) -> PathBuf { - let resolved = PathBuf::from("/") - .join("node_modules") - .join(specifier.clone()) - .join("index.json"); - - package_manager - .expect_resolve() - .with(eq(specifier), eq(from)) - .returning(|specifier, _from| { - Ok(Resolution { - resolved: PathBuf::from("/") - .join("node_modules") - .join(specifier) - .join("index.json"), - }) - }); - - resolved - } - - mod empty_config_and_fallback { - use crate::parcel_config_fixtures::default_config; - use crate::parcel_config_fixtures::default_extended_config; - - use super::*; - - #[test] - fn errors_on_missing_parcelrc_file() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let err = ParcelRcConfigLoader::new(fs, Arc::new(MockPackageManager::new())) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Unable to locate .parcelrc from {}", - project_root.display() - )) - ); - } - - #[test] - fn errors_on_failed_extended_parcelrc_resolution() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let config = default_extended_config(&project_root); - - fs.write_file(&config.base_config.path, config.base_config.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let err = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Failed to resolve extended config @parcel/config-default from {}", - config.base_config.path.display() - )) - ); - } - - #[test] - fn returns_default_parcel_config() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let default_config = default_config(Arc::new(project_root.join(".parcelrc"))); - let files = vec![default_config.path.clone()]; - - fs.write_file(&default_config.path, default_config.parcel_rc); - - let parcel_config = ParcelRcConfigLoader::new(fs, Arc::new(MockPackageManager::default())) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((default_config.parcel_config, files))); - } - - #[test] - fn returns_default_parcel_config_from_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap().join("src").join("packages").join("root"); - - let default_config = default_config(Arc::new(project_root.join(".parcelrc"))); - let files = vec![default_config.path.clone()]; - - fs.write_file(&default_config.path, default_config.parcel_rc); - - let parcel_config = ParcelRcConfigLoader::new(fs, Arc::new(MockPackageManager::default())) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((default_config.parcel_config, files))); - } - - #[test] - fn returns_default_parcel_config_from_project_root_when_outside_cwd() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/root"); - - let default_config = default_config(Arc::new(project_root.join(".parcelrc"))); - let files = vec![default_config.path.clone()]; - - fs.set_current_working_directory(Path::new("/cwd")); - fs.write_file(&default_config.path, default_config.parcel_rc); - - let parcel_config = ParcelRcConfigLoader::new(fs, Arc::new(MockPackageManager::default())) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((default_config.parcel_config, files))); - } - - #[test] - fn returns_merged_default_parcel_config() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let default_config = default_extended_config(&project_root); - let files = vec![ - default_config.base_config.path.clone(), - default_config.extended_config.path.clone(), - ]; - - fs.write_file( - &default_config.base_config.path, - default_config.base_config.parcel_rc, - ); - - fs.write_file( - &default_config.extended_config.path, - default_config.extended_config.parcel_rc, - ); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load(&project_root, LoadConfigOptions::default()) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((default_config.parcel_config, files))); - } - } - - mod config { - use parcel_core::types::Diagnostic; - - use crate::parcel_config_fixtures::config; - use crate::parcel_config_fixtures::extended_config; - - use super::*; - - #[test] - fn errors_on_failed_config_resolution() { - let fs = Arc::new(InMemoryFileSystem::default()); - let mut package_manager = MockPackageManager::new(); - let project_root = fs.cwd().unwrap(); - - fail_package_manager_resolution(&mut package_manager); - - let package_manager = Arc::new(package_manager); - - let err = ParcelRcConfigLoader::new(fs, package_manager) - .load( - &&project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some("@scope/config"), - fallback_config: None, - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Failed to resolve config @scope/config from {}", - project_root.join("index").display() - )) - ); - } - - #[test] - fn errors_on_failed_extended_config_resolution() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (specifier, config) = extended_config(&project_root); - - fs.write_file(&config.base_config.path, config.base_config.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let err = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some(&specifier), - fallback_config: None, - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Failed to resolve extended config @parcel/config-default from {}", - config.base_config.path.display() - )) - ); - } - - #[test] - fn errors_on_missing_config_file() { - let fs = Arc::new(InMemoryFileSystem::default()); - let mut package_manager = MockPackageManager::new(); - let project_root = fs.cwd().unwrap(); - - fs.write_file(&project_root.join(".parcelrc"), String::from("{}")); - - let config_path = package_manager_resolution( - &mut package_manager, - String::from("@scope/config"), - project_root.join("index"), - ); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(package_manager); - - let err = ParcelRcConfigLoader::new(fs, package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some("@scope/config"), - fallback_config: None, - }, - ) - .unwrap_err() - .downcast::() - .expect("Expected diagnostic error"); - - assert_eq!( - err, - DiagnosticBuilder::default() - .code_frames(vec![CodeFrame::from(config_path)]) - .message("File not found") - .origin(Some(String::from("parcel_config::parcel_rc_config_loader"))) - .build() - .unwrap() - ); - } - - #[test] - fn returns_specified_config() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (specifier, specified_config) = config(&project_root); - let files = vec![specified_config.path.clone()]; - - fs.write_file(&project_root.join(".parcelrc"), String::from("{}")); - fs.write_file(&specified_config.path, specified_config.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some(&specifier), - fallback_config: None, - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((specified_config.parcel_config, files))); - } - } - - mod fallback_config { - use parcel_core::types::Diagnostic; - - use crate::parcel_config_fixtures::default_config; - use crate::parcel_config_fixtures::extended_config; - use crate::parcel_config_fixtures::fallback_config; - - use super::*; - - #[test] - fn errors_on_failed_fallback_resolution() { - let fs = Arc::new(InMemoryFileSystem::default()); - let mut package_manager = MockPackageManager::new(); - let project_root = fs.cwd().unwrap(); - - fail_package_manager_resolution(&mut package_manager); - - let package_manager = Arc::new(package_manager); - - let err = ParcelRcConfigLoader::new(fs, package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: None, - fallback_config: Some("@parcel/config-default"), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Failed to resolve fallback @parcel/config-default from {}", - project_root.join("index").display() - )) - ); - } - - #[test] - fn errors_on_failed_extended_fallback_config_resolution() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (fallback_specifier, fallback) = extended_config(&project_root); - - fs.write_file(&fallback.base_config.path, fallback.base_config.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let err = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: None, - fallback_config: Some(&fallback_specifier), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!( - err, - Err(format!( - "Failed to resolve extended config @parcel/config-default from {}", - fallback.base_config.path.display() - )) - ); - } - - #[test] - fn errors_on_missing_fallback_config_file() { - let fs = Arc::new(InMemoryFileSystem::default()); - let mut package_manager = MockPackageManager::new(); - let project_root = fs.cwd().unwrap(); - - let fallback_config_path = package_manager_resolution( - &mut package_manager, - String::from("@parcel/config-default"), - project_root.join("index"), - ); - - let package_manager = Arc::new(package_manager); - - let err = ParcelRcConfigLoader::new(fs, package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: None, - fallback_config: Some("@parcel/config-default"), - }, - ) - .unwrap_err() - .downcast::() - .expect("Expected diagnostic error"); - - assert_eq!( - err, - DiagnosticBuilder::default() - .code_frames(vec![CodeFrame::from(fallback_config_path)]) - .message("File not found") - .origin(Some(String::from("parcel_config::parcel_rc_config_loader"))) - .build() - .unwrap() - ); - } - - #[test] - fn returns_project_root_parcel_rc() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (fallback_specifier, fallback) = fallback_config(&project_root); - let project_root_config = default_config(Arc::new(project_root.join(".parcelrc"))); - - fs.write_file(&project_root_config.path, project_root_config.parcel_rc); - fs.write_file(&fallback.path, String::from("{}")); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: None, - fallback_config: Some(&fallback_specifier), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!( - parcel_config, - Ok(( - project_root_config.parcel_config, - vec!(project_root_config.path) - )) - ); - } - - #[test] - fn returns_fallback_config_when_parcel_rc_is_missing() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (fallback_specifier, fallback) = fallback_config(&project_root); - let files = vec![fallback.path.clone()]; - - fs.write_file(&fallback.path, fallback.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: None, - fallback_config: Some(&fallback_specifier), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((fallback.parcel_config, files))); - } - } - - mod fallback_with_config { - use crate::parcel_config_fixtures::config; - use crate::parcel_config_fixtures::fallback_config; - - use super::*; - - #[test] - fn returns_specified_config() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (config_specifier, config) = config(&project_root); - let (fallback_config_specifier, fallback_config) = fallback_config(&project_root); - - let files = vec![config.path.clone()]; - - fs.write_file(&config.path, config.parcel_rc); - fs.write_file(&fallback_config.path, fallback_config.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some(&config_specifier), - fallback_config: Some(&fallback_config_specifier), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((config.parcel_config, files))); - } - - #[test] - fn returns_fallback_config_when_config_file_missing() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = fs.cwd().unwrap(); - - let (config_specifier, _config) = config(&project_root); - let (fallback_config_specifier, fallback) = fallback_config(&project_root); - - let files = vec![fallback.path.clone()]; - - fs.write_file(&fallback.path, fallback.parcel_rc); - - let fs: FileSystemRef = fs; - let package_manager = Arc::new(TestPackageManager { - fs: Arc::clone(&fs), - }); - - let parcel_config = ParcelRcConfigLoader::new(Arc::clone(&fs), package_manager) - .load( - &project_root, - LoadConfigOptions { - additional_reporters: Vec::new(), - config: Some(&config_specifier), - fallback_config: Some(&fallback_config_specifier), - }, - ) - .map_err(|e| e.to_string()); - - assert_eq!(parcel_config, Ok((fallback.parcel_config, files))); - } - } -} diff --git a/crates/parcel_config/src/partial_parcel_config.rs b/crates/parcel_config/src/partial_parcel_config.rs deleted file mode 100644 index 525de5e0876..00000000000 --- a/crates/parcel_config/src/partial_parcel_config.rs +++ /dev/null @@ -1,693 +0,0 @@ -use std::collections::HashSet; -use std::sync::Arc; - -use derive_builder::Builder; -use indexmap::IndexMap; -use parcel_core::types::DiagnosticError; - -use super::parcel_config::PluginNode; -use super::parcel_rc::ParcelRcFile; - -/// An intermediate representation of the .parcelrc config -/// -/// This data structure is used to perform configuration merging, to eventually create a compelete ParcelConfig. -/// -#[derive(Clone, Debug, Default, Builder, PartialEq)] -#[builder(default)] -pub struct PartialParcelConfig { - pub bundler: Option, - pub compressors: IndexMap>, - pub namers: Vec, - pub optimizers: IndexMap>, - pub packagers: IndexMap, - pub reporters: Vec, - pub resolvers: Vec, - pub runtimes: Vec, - pub transformers: IndexMap>, - pub validators: IndexMap>, -} - -impl TryFrom for PartialParcelConfig { - type Error = DiagnosticError; - - fn try_from(file: ParcelRcFile) -> Result { - // TODO Add validation here: multiple ..., plugin name format, reserved pipelines, etc - - let resolve_from = Arc::new(file.path.clone()); - - let to_entry = |package_name: &String| PluginNode { - package_name: String::from(package_name), - resolve_from: resolve_from.clone(), - }; - - let to_vec = |maybe_plugins: Option<&Vec>| { - maybe_plugins - .map(|plugins| plugins.iter().map(to_entry).collect()) - .unwrap_or(Vec::new()) - }; - - let to_pipelines = |map: Option<&IndexMap>>| { - map - .map(|plugins| { - plugins - .iter() - .map(|(pattern, plugins)| { - ( - String::from(pattern), - plugins.iter().map(to_entry).collect(), - ) - }) - .collect() - }) - .unwrap_or(IndexMap::new()) - }; - - let to_pipeline = |map: Option<&IndexMap>| { - map - .map(|plugins| { - plugins - .iter() - .map(|(pattern, package_name)| (String::from(pattern), to_entry(package_name))) - .collect() - }) - .unwrap_or(IndexMap::new()) - }; - - Ok(PartialParcelConfig { - bundler: file - .contents - .bundler - .as_ref() - .map(|package_name| PluginNode { - package_name: String::from(package_name), - resolve_from: resolve_from.clone(), - }), - compressors: to_pipelines(file.contents.compressors.as_ref()), - namers: to_vec(file.contents.namers.as_ref()), - optimizers: to_pipelines(file.contents.optimizers.as_ref()), - packagers: to_pipeline(file.contents.packagers.as_ref()), - reporters: to_vec(file.contents.reporters.as_ref()), - resolvers: to_vec(file.contents.resolvers.as_ref()), - runtimes: to_vec(file.contents.runtimes.as_ref()), - transformers: to_pipelines(file.contents.transformers.as_ref()), - validators: to_pipelines(file.contents.validators.as_ref()), - }) - } -} - -impl PartialParcelConfig { - fn merge_map( - map: IndexMap, - extend_map: IndexMap, - merge: fn(map: T, extend_map: T) -> T, - ) -> IndexMap { - if extend_map.is_empty() { - return map; - } - - if map.is_empty() { - return extend_map; - } - - let mut merged_map = IndexMap::new(); - let mut used_patterns = HashSet::new(); - - // Add the extension options first so they have higher precedence in the output glob map - for (pattern, extend_pipelines) in extend_map { - let map_pipelines = map.get(&pattern); - if let Some(pipelines) = map_pipelines { - used_patterns.insert(pattern.clone()); - merged_map.insert(pattern, merge(pipelines.clone(), extend_pipelines)); - } else { - merged_map.insert(pattern, extend_pipelines); - } - } - - // Add remaining pipelines - for (pattern, value) in map { - if !used_patterns.contains(&pattern) { - merged_map.insert(String::from(pattern), value); - } - } - - merged_map - } - - fn merge_pipeline_map( - map: IndexMap, - extend_map: IndexMap, - ) -> IndexMap { - PartialParcelConfig::merge_map(map, extend_map, |map, _extend_map| map) - } - - fn merge_pipelines_map( - from_map: IndexMap>, - extend_map: IndexMap>, - ) -> IndexMap> { - PartialParcelConfig::merge_map(from_map, extend_map, PartialParcelConfig::merge_pipelines) - } - - fn merge_pipelines( - from_pipelines: Vec, - extend_pipelines: Vec, - ) -> Vec { - if extend_pipelines.is_empty() { - return from_pipelines; - } - - if from_pipelines.is_empty() { - return extend_pipelines; - } - - let spread_index = from_pipelines - .iter() - .position(|plugin| plugin.package_name == "..."); - - match spread_index { - None => from_pipelines, - Some(index) => { - let extend_pipelines = extend_pipelines.as_slice(); - - [ - &from_pipelines[..index], - extend_pipelines, - &from_pipelines[(index + 1)..], - ] - .concat() - } - } - } - - pub fn merge(from_config: PartialParcelConfig, extend_config: PartialParcelConfig) -> Self { - PartialParcelConfig { - bundler: from_config.bundler.or(extend_config.bundler), - compressors: PartialParcelConfig::merge_pipelines_map( - from_config.compressors, - extend_config.compressors, - ), - namers: PartialParcelConfig::merge_pipelines(from_config.namers, extend_config.namers), - optimizers: PartialParcelConfig::merge_pipelines_map( - from_config.optimizers, - extend_config.optimizers, - ), - packagers: PartialParcelConfig::merge_pipeline_map( - from_config.packagers, - extend_config.packagers, - ), - reporters: PartialParcelConfig::merge_pipelines( - from_config.reporters, - extend_config.reporters, - ), - resolvers: PartialParcelConfig::merge_pipelines( - from_config.resolvers, - extend_config.resolvers, - ), - runtimes: PartialParcelConfig::merge_pipelines(from_config.runtimes, extend_config.runtimes), - transformers: PartialParcelConfig::merge_pipelines_map( - from_config.transformers, - extend_config.transformers, - ), - validators: PartialParcelConfig::merge_pipelines_map( - from_config.validators, - extend_config.validators, - ), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod merge { - use super::*; - - mod bundler { - use std::path::PathBuf; - use std::sync::Arc; - - use super::*; - - #[test] - fn uses_from_when_extend_missing() { - let from = PartialParcelConfigBuilder::default() - .bundler(Some(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - })) - .build() - .unwrap(); - - let extend = PartialParcelConfig::default(); - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn uses_extend_when_from_missing() { - let from = PartialParcelConfig::default(); - let extend = PartialParcelConfigBuilder::default() - .bundler(Some(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - })) - .build() - .unwrap(); - - let expected = extend.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn merges_using_from() { - let from = PartialParcelConfigBuilder::default() - .bundler(Some(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - })) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .bundler(Some(PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("/")), - })) - .build() - .unwrap(); - - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - } - - macro_rules! test_pipeline_map { - ($property: ident) => { - #[cfg(test)] - mod $property { - use std::path::PathBuf; - - use indexmap::indexmap; - - use super::*; - - #[test] - fn uses_from_when_extend_missing() { - let from = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let extend = PartialParcelConfig::default(); - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn uses_extend_when_from_missing() { - let from = PartialParcelConfig::default(); - let extend = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let expected = extend.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn merges_patterns() { - let from = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.{cjs,js,mjs}") => vec!(PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }) - }) - .build() - .unwrap(); - - assert_eq!( - PartialParcelConfig::merge(from, extend), - PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }), - String::from("*.{cjs,js,mjs}") => vec!(PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }), - }) - .build() - .unwrap() - ); - } - - #[test] - fn merges_pipelines_with_missing_dot_dot_dot() { - let from = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn merges_pipelines_with_dot_dot_dot() { - let from = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("..."), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }) - }) - .build() - .unwrap(); - - assert_eq!( - PartialParcelConfig::merge(from, extend), - PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap() - ); - } - - #[test] - fn merges_pipelines_with_dot_dot_dot_match_in_grandparent() { - let from = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("..."), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap(); - - let extend_1 = PartialParcelConfig::default(); - let extend_2 = PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }) - }) - .build() - .unwrap(); - - assert_eq!( - PartialParcelConfig::merge(PartialParcelConfig::merge(from, extend_1), extend_2), - PartialParcelConfigBuilder::default() - .$property(indexmap! { - String::from("*.js") => vec!(PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }) - }) - .build() - .unwrap() - ); - } - } - }; - } - - macro_rules! test_pipelines { - ($property: ident) => { - #[cfg(test)] - mod $property { - use std::path::PathBuf; - - use super::*; - - #[test] - fn uses_from_when_extend_missing() { - let from = PartialParcelConfigBuilder::default() - .$property(vec![PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }]) - .build() - .unwrap(); - - let extend = PartialParcelConfig::default(); - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn uses_extend_when_from_missing() { - let from = PartialParcelConfig::default(); - let extend = PartialParcelConfigBuilder::default() - .$property(vec![PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }]) - .build() - .unwrap(); - - let expected = extend.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn merges_pipelines_with_missing_dot_dot_dot() { - let from = PartialParcelConfigBuilder::default() - .$property(vec![ - PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - ]) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .$property(vec![PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }]) - .build() - .unwrap(); - - let expected = from.clone(); - - assert_eq!(PartialParcelConfig::merge(from, extend), expected); - } - - #[test] - fn merges_pipelines_with_dot_dot_dot() { - let from = PartialParcelConfigBuilder::default() - .$property(vec![ - PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("..."), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - ]) - .build() - .unwrap(); - - let extend = PartialParcelConfigBuilder::default() - .$property(vec![PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }]) - .build() - .unwrap(); - - assert_eq!( - PartialParcelConfig::merge(from, extend), - PartialParcelConfigBuilder::default() - .$property(vec!( - PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - } - )) - .build() - .unwrap() - ); - } - - #[test] - fn merges_pipelines_with_dot_dot_dot_match_in_grandparent() { - let from = PartialParcelConfigBuilder::default() - .$property(vec![ - PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("..."), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - ]) - .build() - .unwrap(); - - let extend_1 = PartialParcelConfig::default(); - let extend_2 = PartialParcelConfigBuilder::default() - .$property(vec![PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }]) - .build() - .unwrap(); - - assert_eq!( - PartialParcelConfig::merge(PartialParcelConfig::merge(from, extend_1), extend_2), - PartialParcelConfigBuilder::default() - .$property(vec!( - PluginNode { - package_name: String::from("a"), - resolve_from: Arc::new(PathBuf::from("/")), - }, - PluginNode { - package_name: String::from("b"), - resolve_from: Arc::new(PathBuf::from("~")), - }, - PluginNode { - package_name: String::from("c"), - resolve_from: Arc::new(PathBuf::from("/")), - } - )) - .build() - .unwrap() - ); - } - } - }; - } - - test_pipeline_map!(compressors); - test_pipelines!(namers); - test_pipeline_map!(optimizers); - test_pipelines!(reporters); - test_pipelines!(resolvers); - test_pipelines!(runtimes); - test_pipeline_map!(transformers); - test_pipeline_map!(validators); - } -} diff --git a/crates/parcel_core/Cargo.toml b/crates/parcel_core/Cargo.toml deleted file mode 100644 index c01f2a5765c..00000000000 --- a/crates/parcel_core/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "parcel_core" -version = "0.1.0" -edition = "2021" -description = "Core logic and types for the parcel bundler" - -[features] -default = [] - -[dependencies] -parcel_filesystem = { path = "../parcel_filesystem" } - -anyhow = "1.0.82" -bitflags = "2.5.0" -browserslist-rs = "0.16.0" -derive_builder = "0.20.0" -dyn-hash = "0.x" -nodejs-semver = "4.0.0" -mockall = "0.12.1" -petgraph = { version = "0.6.5", features = ["serde-1"] } -serde = { version = "1.0.200", features = ["derive", "rc"] } -serde_json = { version = "1.0.116", features = ["preserve_order"] } -serde_repr = "0.1.19" -serde-value = "0.7.0" -xxhash-rust = { version = "0.8.2", features = ["xxh3"] } diff --git a/crates/parcel_core/src/asset_graph.rs b/crates/parcel_core/src/asset_graph.rs deleted file mode 100644 index 375b54729cc..00000000000 --- a/crates/parcel_core/src/asset_graph.rs +++ /dev/null @@ -1,606 +0,0 @@ -use std::{collections::HashSet, sync::Arc}; - -use petgraph::{ - graph::{DiGraph, NodeIndex}, - visit::EdgeRef, - Direction, -}; - -use crate::types::{Asset, Dependency}; - -#[derive(Clone, Debug)] -pub struct AssetGraph { - graph: DiGraph, - pub assets: Vec, - pub dependencies: Vec, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct AssetNode { - pub asset: Asset, - pub requested_symbols: HashSet, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct DependencyNode { - pub dependency: Arc, - pub requested_symbols: HashSet, - pub state: DependencyState, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum AssetGraphNode { - Root, - Entry, - Asset(usize), - Dependency(usize), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct AssetGraphEdge {} - -#[derive(Clone, Debug, PartialEq)] -pub enum DependencyState { - New, - Deferred, - Excluded, - Resolved, -} - -impl PartialEq for AssetGraph { - fn eq(&self, other: &Self) -> bool { - let nodes = self.graph.raw_nodes().iter().map(|n| &n.weight); - let other_nodes = other.graph.raw_nodes().iter().map(|n| &n.weight); - - let edges = self - .graph - .raw_edges() - .iter() - .map(|e| (e.source(), e.target(), &e.weight)); - - let other_edges = other - .graph - .raw_edges() - .iter() - .map(|e| (e.source(), e.target(), &e.weight)); - - nodes.eq(other_nodes) - && edges.eq(other_edges) - && self.assets == other.assets - && self.dependencies == other.dependencies - } -} - -impl AssetGraph { - pub fn new() -> Self { - let mut graph = DiGraph::new(); - - graph.add_node(AssetGraphNode::Root); - - AssetGraph { - graph, - assets: Vec::new(), - dependencies: Vec::new(), - } - } - - pub fn add_asset(&mut self, parent_idx: NodeIndex, asset: Asset) -> NodeIndex { - let idx = self.assets.len(); - - self.assets.push(AssetNode { - asset, - requested_symbols: HashSet::default(), - }); - - let asset_idx = self.graph.add_node(AssetGraphNode::Asset(idx)); - - self - .graph - .add_edge(parent_idx, asset_idx, AssetGraphEdge {}); - - asset_idx - } - - pub fn add_entry_dependency(&mut self, dependency: Dependency) -> NodeIndex { - // The root node index will always be 0 - let root_node_index = NodeIndex::new(0); - - let is_library = dependency.env.is_library; - let node_index = self.add_dependency(root_node_index, dependency); - - if is_library { - if let Some(dependency_index) = &self.dependency_index(node_index) { - self.dependencies[*dependency_index] - .requested_symbols - .insert("*".into()); - } - } - - node_index - } - - pub fn add_dependency(&mut self, parent_idx: NodeIndex, dependency: Dependency) -> NodeIndex { - let idx = self.dependencies.len(); - - self.dependencies.push(DependencyNode { - dependency: Arc::new(dependency), - requested_symbols: HashSet::default(), - state: DependencyState::New, - }); - - let dependency_idx = self.graph.add_node(AssetGraphNode::Dependency(idx)); - - self - .graph - .add_edge(parent_idx, dependency_idx, AssetGraphEdge {}); - - dependency_idx - } - - pub fn add_edge(&mut self, parent_idx: &NodeIndex, child_idx: &NodeIndex) { - self - .graph - .add_edge(*parent_idx, *child_idx, AssetGraphEdge {}); - } - - pub fn dependency_index(&self, node_index: NodeIndex) -> Option { - match self.graph.node_weight(node_index).unwrap() { - AssetGraphNode::Dependency(idx) => Some(*idx), - _ => None, - } - } - - pub fn asset_index(&self, node_index: NodeIndex) -> Option { - match self.graph.node_weight(node_index).unwrap() { - AssetGraphNode::Asset(idx) => Some(*idx), - _ => None, - } - } - - /// Propagates the requested symbols from an incoming dependency to an asset, - /// and forwards those symbols to re-exported dependencies if needed. - /// This may result in assets becoming un-deferred and transformed if they - /// now have requested symbols. - pub fn propagate_requested_symbols)>( - &mut self, - asset_node: NodeIndex, - incoming_dep_node: NodeIndex, - on_undeferred: &mut F, - ) { - let DependencyNode { - requested_symbols, .. - } = &self.dependencies[self.dependency_index(incoming_dep_node).unwrap()]; - - let asset_index = self.asset_index(asset_node).unwrap(); - let AssetNode { - asset, - requested_symbols: asset_requested_symbols, - } = &mut self.assets[asset_index]; - - let mut re_exports = HashSet::::default(); - let mut wildcards = HashSet::::default(); - let star = String::from("*"); - - if requested_symbols.contains(&star) { - // If the requested symbols includes the "*" namespace, - // we need to include all of the asset's exported symbols. - for sym in &asset.symbols { - if asset_requested_symbols.insert(sym.exported.clone()) && sym.is_weak { - // Propagate re-exported symbol to dependency. - re_exports.insert(sym.local.clone()); - } - } - - // Propagate to all export * wildcard dependencies. - wildcards.insert(star); - } else { - // Otherwise, add each of the requested symbols to the asset. - for sym in requested_symbols.iter() { - if asset_requested_symbols.insert(sym.clone()) { - if let Some(asset_symbol) = asset.symbols.iter().find(|s| s.exported == *sym) { - if asset_symbol.is_weak { - // Propagate re-exported symbol to dependency. - re_exports.insert(asset_symbol.local.clone()); - } - } else { - // If symbol wasn't found in the asset or a named re-export. - // This means the symbol is in one of the export * wildcards, but we don't know - // which one yet, so we propagate it to _all_ wildcard dependencies. - wildcards.insert(sym.clone()); - } - } - } - } - - let deps: Vec<_> = self - .graph - .neighbors_directed(asset_node, Direction::Outgoing) - .collect(); - for dep_node in deps { - let dep_index = self.dependency_index(dep_node).unwrap(); - let DependencyNode { - dependency, - requested_symbols, - state, - } = &mut self.dependencies[dep_index]; - - let mut updated = false; - for sym in &dependency.symbols { - if sym.is_weak { - // This is a re-export. If it is a wildcard, add all unmatched symbols - // to this dependency, otherwise attempt to match a named re-export. - if sym.local == "*" { - for wildcard in &wildcards { - if requested_symbols.insert(wildcard.clone()) { - updated = true; - } - } - } else if re_exports.contains(&sym.local) - && requested_symbols.insert(sym.exported.clone()) - { - updated = true; - } - } else if requested_symbols.insert(sym.exported.clone()) { - // This is a normal import. Add the requested symbol. - updated = true; - } - } - - // If the dependency was updated, propagate to the target asset if there is one, - // or un-defer this dependency so we transform the requested asset. - // We must always resolve new dependencies to determine whether they have side effects. - if updated || *state == DependencyState::New { - if let Some(resolved) = self - .graph - .edges_directed(dep_node, Direction::Outgoing) - .next() - { - self.propagate_requested_symbols(resolved.target(), dep_node, on_undeferred); - } else { - on_undeferred(dep_node, Arc::clone(&dependency)); - } - } - } - } -} - -#[derive(serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SerializedAsset { - id: String, - asset: Asset, -} - -#[derive(serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SerializedDependency { - id: String, - dependency: Dependency, -} - -impl serde::Serialize for AssetGraph { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let nodes: Vec<_> = self - .graph - .node_weights() - .map(|node| match node { - AssetGraphNode::Root => SerializedAssetGraphNode::Root, - AssetGraphNode::Entry => SerializedAssetGraphNode::Entry, - AssetGraphNode::Asset(idx) => { - let asset = self.assets[*idx].asset.clone(); - - SerializedAssetGraphNode::Asset { - value: SerializedAsset { - id: asset.id().to_string(), - asset, - }, - } - } - AssetGraphNode::Dependency(idx) => { - let dependency = self.dependencies[*idx].dependency.clone(); - SerializedAssetGraphNode::Dependency { - value: SerializedDependency { - id: dependency.id().to_string(), - dependency: dependency.as_ref().clone(), - }, - has_deferred: self.dependencies[*idx].state == DependencyState::Deferred, - } - } - }) - .collect(); - let raw_edges = self.graph.raw_edges(); - let mut edges = Vec::with_capacity(raw_edges.len() * 2); - for edge in raw_edges { - edges.push(edge.source().index() as u32); - edges.push(edge.target().index() as u32); - } - - #[derive(serde::Serialize)] - #[serde(tag = "type", rename_all = "camelCase")] - enum SerializedAssetGraphNode { - Root, - Entry, - Asset { - value: SerializedAsset, - }, - Dependency { - value: SerializedDependency, - has_deferred: bool, - }, - } - - #[derive(serde::Serialize)] - struct SerializedAssetGraph { - nodes: Vec, - // TODO: somehow make this a typed array? - edges: Vec, - } - - let serialized = SerializedAssetGraph { nodes, edges }; - serialized.serialize(serializer) - } -} - -impl std::hash::Hash for AssetGraph { - fn hash(&self, state: &mut H) { - for node in self.graph.node_weights() { - std::mem::discriminant(node).hash(state); - match node { - AssetGraphNode::Asset(idx) => self.assets[*idx].asset.id().hash(state), - AssetGraphNode::Dependency(idx) => self.dependencies[*idx].dependency.id().hash(state), - _ => {} - } - } - } -} - -#[cfg(test)] -mod test { - use std::path::PathBuf; - - use crate::types::{Symbol, Target}; - - use super::*; - - type TestSymbol<'a> = (&'a str, &'a str, bool); - fn symbol(test_symbol: &TestSymbol) -> Symbol { - let (local, exported, is_weak) = test_symbol; - Symbol { - local: String::from(*local), - exported: String::from(*exported), - is_weak: is_weak.to_owned(), - ..Symbol::default() - } - } - - fn assert_requested_symbols(graph: &AssetGraph, node_index: NodeIndex, expected: Vec<&str>) { - assert_eq!( - graph.dependencies[graph.dependency_index(node_index).unwrap()].requested_symbols, - expected.into_iter().map(|s| s.into()).collect() - ); - } - - fn add_asset( - graph: &mut AssetGraph, - parent_node: NodeIndex, - symbols: Vec, - file_path: &str, - ) -> NodeIndex { - let index_asset = Asset { - file_path: PathBuf::from(file_path), - symbols: symbols.iter().map(symbol).collect(), - ..Asset::default() - }; - graph.add_asset(parent_node, index_asset) - } - - fn add_dependency( - graph: &mut AssetGraph, - parent_node: NodeIndex, - symbols: Vec, - ) -> NodeIndex { - let dep = Dependency { - symbols: symbols.iter().map(symbol).collect(), - ..Dependency::default() - }; - graph.add_dependency(parent_node, dep) - } - - #[test] - fn should_request_entry_asset() { - let mut requested = HashSet::new(); - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - let index_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "index.js"); - let dep_a_node = add_dependency(&mut graph, index_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols( - index_asset_node, - entry_dep_node, - &mut |dependency_node_index, _dependency| { - requested.insert(dependency_node_index); - }, - ); - - assert_eq!(requested, HashSet::from_iter(vec![dep_a_node])); - assert_requested_symbols(&graph, dep_a_node, vec!["a"]); - } - - #[test] - fn should_propagate_named_reexports() { - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - // entry.js imports "a" from library.js - let entry_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "entry.js"); - let library_dep_node = add_dependency(&mut graph, entry_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols(entry_asset_node, entry_dep_node, &mut |_, _| {}); - - // library.js re-exports "a" from a.js and "b" from b.js - // only "a" is used in entry.js - let library_asset_node = add_asset( - &mut graph, - library_dep_node, - vec![("a", "a", true), ("b", "b", true)], - "library.js", - ); - let a_dep = add_dependency(&mut graph, library_asset_node, vec![("a", "a", true)]); - let b_dep = add_dependency(&mut graph, library_asset_node, vec![("b", "b", true)]); - - let mut requested_deps = Vec::new(); - graph.propagate_requested_symbols( - library_asset_node, - library_dep_node, - &mut |dependency_node_index, _dependency| { - requested_deps.push(dependency_node_index); - }, - ); - assert_eq!( - requested_deps, - vec![b_dep, a_dep], - "Should request both new deps" - ); - - // "a" should be the only requested symbol - assert_requested_symbols(&graph, library_dep_node, vec!["a"]); - assert_requested_symbols(&graph, a_dep, vec!["a"]); - assert_requested_symbols(&graph, b_dep, vec![]); - } - - #[test] - fn should_propagate_wildcard_reexports() { - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - // entry.js imports "a" from library.js - let entry_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "entry.js"); - let library_dep_node = add_dependency(&mut graph, entry_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols(entry_asset_node, entry_dep_node, &mut |_, _| {}); - - // library.js re-exports "*" from a.js and "*" from b.js - // only "a" is used in entry.js - let library_asset_node = add_asset(&mut graph, library_dep_node, vec![], "library.js"); - let a_dep = add_dependency(&mut graph, library_asset_node, vec![("*", "*", true)]); - let b_dep = add_dependency(&mut graph, library_asset_node, vec![("*", "*", true)]); - - let mut requested_deps = Vec::new(); - graph.propagate_requested_symbols( - library_asset_node, - library_dep_node, - &mut |dependency_node_index, _dependency| { - requested_deps.push(dependency_node_index); - }, - ); - assert_eq!( - requested_deps, - vec![b_dep, a_dep], - "Should request both new deps" - ); - - // "a" should be marked as requested on all deps as wildcards make it - // unclear who the owning dep is - assert_requested_symbols(&graph, library_dep_node, vec!["a"]); - assert_requested_symbols(&graph, a_dep, vec!["a"]); - assert_requested_symbols(&graph, b_dep, vec!["a"]); - } - - #[test] - fn should_propagate_nested_reexports() { - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - // entry.js imports "a" from library - let entry_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "entry.js"); - let library_dep_node = add_dependency(&mut graph, entry_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols(entry_asset_node, entry_dep_node, &mut |_, _| {}); - - // library.js re-exports "*" from library/index.js - let library_entry_asset_node = add_asset(&mut graph, library_dep_node, vec![], "library.js"); - let library_reexport_dep_node = - add_dependency(&mut graph, library_entry_asset_node, vec![("*", "*", true)]); - graph.propagate_requested_symbols(library_entry_asset_node, library_dep_node, &mut |_, _| {}); - - // library/index.js re-exports "a" from a.js - let library_asset_node = add_asset( - &mut graph, - library_reexport_dep_node, - vec![("a", "a", true)], - "library/index.js", - ); - let a_dep = add_dependency(&mut graph, library_asset_node, vec![("a", "a", true)]); - graph.propagate_requested_symbols(library_entry_asset_node, library_dep_node, &mut |_, _| {}); - - // "a" should be marked as requested on all deps until the a dep is reached - assert_requested_symbols(&graph, library_dep_node, vec!["a"]); - assert_requested_symbols(&graph, library_reexport_dep_node, vec!["a"]); - assert_requested_symbols(&graph, a_dep, vec!["a"]); - } - - #[test] - fn should_propagate_renamed_reexports() { - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - // entry.js imports "a" from library - let entry_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "entry.js"); - let library_dep_node = add_dependency(&mut graph, entry_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols(entry_asset_node, entry_dep_node, &mut |_, _| {}); - - // library.js re-exports "b" from b.js renamed as "a" - let library_asset_node = add_asset( - &mut graph, - library_dep_node, - vec![("b", "a", true)], - "library.js", - ); - let b_dep = add_dependency(&mut graph, library_asset_node, vec![("b", "b", true)]); - graph.propagate_requested_symbols(library_asset_node, library_dep_node, &mut |_, _| {}); - - // "a" should be marked as requested on the library dep - assert_requested_symbols(&graph, library_dep_node, vec!["a"]); - // "b" should be marked as requested on the b dep - assert_requested_symbols(&graph, b_dep, vec!["b"]); - } - - #[test] - fn should_propagate_namespace_reexports() { - let mut graph = AssetGraph::new(); - let target = Target::default(); - let dep = Dependency::entry(String::from("index.js"), target); - let entry_dep_node = graph.add_entry_dependency(dep); - - // entry.js imports "a" from library - let entry_asset_node = add_asset(&mut graph, entry_dep_node, vec![], "entry.js"); - let library_dep_node = add_dependency(&mut graph, entry_asset_node, vec![("a", "a", false)]); - graph.propagate_requested_symbols(entry_asset_node, entry_dep_node, &mut |_, _| {}); - - // library.js re-exports "*" from stuff.js renamed as "a"" - // export * as a from './stuff.js' - let library_asset_node = add_asset( - &mut graph, - library_dep_node, - vec![("a", "a", true)], - "library.js", - ); - let stuff_dep = add_dependency(&mut graph, library_asset_node, vec![("a", "*", true)]); - graph.propagate_requested_symbols(library_asset_node, library_dep_node, &mut |_, _| {}); - - // "a" should be marked as requested on the library dep - assert_requested_symbols(&graph, library_dep_node, vec!["a"]); - // "*" should be marked as requested on the stuff dep - assert_requested_symbols(&graph, stuff_dep, vec!["*"]); - } -} diff --git a/crates/parcel_core/src/bundle_graph.rs b/crates/parcel_core/src/bundle_graph.rs deleted file mode 100644 index 0dccafb6497..00000000000 --- a/crates/parcel_core/src/bundle_graph.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct BundleGraph {} diff --git a/crates/parcel_core/src/cache.rs b/crates/parcel_core/src/cache.rs deleted file mode 100644 index 2aa8440a99d..00000000000 --- a/crates/parcel_core/src/cache.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::sync::Arc; - -pub type CacheRef = Arc; - -#[mockall::automock] -pub trait Cache { - fn set_blob(&self, _key: &str, _blob: &[u8]) -> anyhow::Result<()>; -} diff --git a/crates/parcel_core/src/config_loader.rs b/crates/parcel_core/src/config_loader.rs deleted file mode 100644 index 3cc08d6df23..00000000000 --- a/crates/parcel_core/src/config_loader.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use parcel_filesystem::search::find_ancestor_file; -use parcel_filesystem::FileSystemRef; -use serde::de::DeserializeOwned; - -use crate::{ - diagnostic_error, - types::{CodeFrame, CodeHighlight, DiagnosticBuilder, DiagnosticError, ErrorKind, File}, -}; - -pub type ConfigLoaderRef = Arc; - -/// Enables config to be loaded in various formats -pub struct ConfigLoader { - pub fs: FileSystemRef, - pub project_root: PathBuf, - pub search_path: PathBuf, -} - -#[derive(Debug, PartialEq)] -pub struct ConfigFile { - pub contents: T, - pub path: PathBuf, - pub raw: String, -} - -// TODO JavaScript configs, invalidations, dev deps, etc -impl ConfigLoader { - pub fn load_json_config( - &self, - filename: &str, - ) -> Result, DiagnosticError> { - let path = find_ancestor_file( - &*self.fs, - &[filename], - &self.search_path, - &self.project_root, - ) - .ok_or_else(|| { - diagnostic_error!(DiagnosticBuilder::default() - .kind(ErrorKind::NotFound) - .message(format!( - "Unable to locate {filename} config file from {}", - self.search_path.display() - ))) - })?; - - let code = self.fs.read_to_string(&path)?; - - let contents = serde_json::from_str::(&code).map_err(|error| { - diagnostic_error!(DiagnosticBuilder::default() - .code_frames(vec![CodeFrame { - code_highlights: vec![CodeHighlight::from([error.line(), error.column()])], - ..CodeFrame::from(File { - contents: code.clone(), - path: path.clone() - }) - }]) - .message(format!("{error} in {}", path.display()))) - })?; - - Ok(ConfigFile { - contents, - path, - raw: code, - }) - } - - pub fn load_package_json( - &self, - ) -> Result, anyhow::Error> { - self.load_json_config::("package.json") - } -} - -#[cfg(test)] -mod tests { - use parcel_filesystem::in_memory_file_system::InMemoryFileSystem; - - use super::*; - - mod load_json_config { - use std::sync::Arc; - - use serde::Deserialize; - - use super::*; - - #[derive(Debug, Deserialize, PartialEq)] - struct JsonConfig {} - - #[test] - fn returns_an_error_when_the_config_does_not_exist() { - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - - let config = ConfigLoader { - fs: Arc::new(InMemoryFileSystem::default()), - project_root, - search_path: search_path.clone(), - }; - - assert_eq!( - config - .load_json_config::("config.json") - .map_err(|err| err.to_string()), - Err(format!( - "Unable to locate config.json config file from {}", - search_path.display() - )) - ) - } - - #[test] - fn returns_an_error_when_the_config_is_outside_the_search_path() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - - fs.write_file( - &search_path.join("packages").join("config.json"), - String::from("{}"), - ); - - let config = ConfigLoader { - fs, - project_root: PathBuf::default(), - search_path: search_path.clone(), - }; - - assert_eq!( - config - .load_json_config::("config.json") - .map_err(|err| err.to_string()), - Err(format!( - "Unable to locate config.json config file from {}", - search_path.display() - )) - ) - } - - #[test] - fn returns_an_error_when_the_config_is_outside_the_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - - fs.write_file(&PathBuf::from("config.json"), String::from("{}")); - - let config = ConfigLoader { - fs, - project_root, - search_path: search_path.clone(), - }; - - assert_eq!( - config - .load_json_config::("config.json") - .map_err(|err| err.to_string()), - Err(format!( - "Unable to locate config.json config file from {}", - search_path.display() - )) - ) - } - - #[test] - fn returns_json_config_at_search_path() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let config_path = search_path.join("config.json"); - - fs.write_file(&config_path, String::from("{}")); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_json_config::("config.json") - .map_err(|err| err.to_string()), - Ok(ConfigFile { - path: config_path, - contents: JsonConfig {}, - raw: String::from("{}") - }) - ) - } - - #[test] - fn returns_json_config_at_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let config_path = project_root.join("config.json"); - - fs.write_file(&config_path, String::from("{}")); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_json_config::("config.json") - .map_err(|err| err.to_string()), - Ok(ConfigFile { - path: config_path, - contents: JsonConfig {}, - raw: String::from("{}") - }) - ) - } - } - - mod load_package_json_config { - use std::sync::Arc; - - use super::*; - - fn package_json() -> String { - String::from( - r#" - { - "name": "parcel", - "version": "1.0.0", - "plugin": { - "enabled": true - } - } - "#, - ) - } - - fn package_config() -> PackageJsonConfig { - PackageJsonConfig { - plugin: PluginConfig { enabled: true }, - } - } - - #[derive(Debug, PartialEq, serde::Deserialize)] - struct PluginConfig { - enabled: bool, - } - - #[derive(Debug, PartialEq, serde::Deserialize)] - struct PackageJsonConfig { - plugin: PluginConfig, - } - - #[test] - fn returns_an_error_when_package_json_does_not_exist() { - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - - let config = ConfigLoader { - fs: Arc::new(InMemoryFileSystem::default()), - project_root, - search_path: search_path.clone(), - }; - - assert_eq!( - config - .load_package_json::() - .map_err(|err| err.to_string()), - Err(format!( - "Unable to locate package.json config file from {}", - search_path.display() - )) - ) - } - - #[test] - fn returns_an_error_when_config_key_does_not_exist_at_search_path() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let package_path = search_path.join("package.json"); - - fs.write_file(&package_path, String::from("{}")); - fs.write_file(&project_root.join("package.json"), package_json()); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_package_json::() - .map_err(|err| err.to_string()), - Err(format!( - "missing field `plugin` at line 1 column 2 in {}", - package_path.display() - )) - ) - } - - #[test] - fn returns_an_error_when_config_key_does_not_exist_at_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let package_path = project_root.join("package.json"); - - fs.write_file(&package_path, String::from("{}")); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_package_json::() - .map_err(|err| err.to_string()), - Err(format!( - "missing field `plugin` at line 1 column 2 in {}", - package_path.display() - )) - ) - } - - #[test] - fn returns_package_config_at_search_path() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let package_path = search_path.join("package.json"); - - fs.write_file(&package_path, package_json()); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_package_json::() - .map_err(|err| err.to_string()), - Ok(ConfigFile { - path: package_path, - contents: package_config(), - raw: package_json() - }) - ) - } - - #[test] - fn returns_package_config_at_project_root() { - let fs = Arc::new(InMemoryFileSystem::default()); - let project_root = PathBuf::from("/project-root"); - let search_path = project_root.join("index"); - let package_path = project_root.join("package.json"); - - fs.write_file(&package_path, package_json()); - - let config = ConfigLoader { - fs, - project_root, - search_path, - }; - - assert_eq!( - config - .load_package_json::() - .map_err(|err| err.to_string()), - Ok(ConfigFile { - path: package_path, - contents: package_config(), - raw: package_json() - }) - ) - } - } -} diff --git a/crates/parcel_core/src/hash.rs b/crates/parcel_core/src/hash.rs deleted file mode 100644 index 1ae73da4e11..00000000000 --- a/crates/parcel_core/src/hash.rs +++ /dev/null @@ -1,18 +0,0 @@ -use xxhash_rust::xxh3::xxh3_64; -use xxhash_rust::xxh3::Xxh3; - -/// Parcel needs to use a hasher for generating certain identifiers used in caches. -/// -/// The hashes don't need to be incredibly fast, but they should be stable across -/// runs, machines, platforms and versions. -/// -/// These hashes will likely end-up being written to disk, either within output -/// JavaScript files or internal caches. -pub type IdentifierHasher = Xxh3; - -/// Copy of one of the `node-bindings/src/hash.rs` functions. -pub fn hash_string(s: String) -> String { - let s = s.as_bytes(); - let res = xxh3_64(s); - format!("{:016x}", res) -} diff --git a/crates/parcel_core/src/lib.rs b/crates/parcel_core/src/lib.rs deleted file mode 100644 index 3411edb305e..00000000000 --- a/crates/parcel_core/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod asset_graph; -pub mod bundle_graph; -pub mod cache; -pub mod config_loader; -pub mod hash; -pub mod plugin; -pub mod types; diff --git a/crates/parcel_core/src/plugin.rs b/crates/parcel_core/src/plugin.rs deleted file mode 100644 index 6e8129feba8..00000000000 --- a/crates/parcel_core/src/plugin.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; - -mod bundler_plugin; -pub use bundler_plugin::*; - -mod compressor_plugin; -pub use compressor_plugin::*; - -mod namer_plugin; -pub use namer_plugin::*; - -mod optimizer_plugin; -pub use optimizer_plugin::*; - -mod packager_plugin; -pub use packager_plugin::*; - -mod reporter_plugin; -use parcel_filesystem::FileSystemRef; -pub use reporter_plugin::*; - -mod resolver_plugin; -pub use resolver_plugin::*; - -mod runtime_plugin; -pub use runtime_plugin::*; - -mod transformer_plugin; -pub use transformer_plugin::*; - -mod validator_plugin; -pub use validator_plugin::*; - -use crate::config_loader::{ConfigLoader, ConfigLoaderRef}; -use crate::types::{BuildMode, LogLevel}; - -pub struct PluginContext { - pub config: ConfigLoaderRef, - pub file_system: FileSystemRef, - pub logger: PluginLogger, - pub options: Arc, -} - -#[derive(Default)] -pub struct PluginLogger {} - -#[derive(Debug, Default)] -pub struct PluginOptions { - pub core_path: PathBuf, - pub env: Option>, - pub log_level: LogLevel, - pub mode: BuildMode, - pub project_root: PathBuf, -} diff --git a/crates/parcel_core/src/plugin/bundler_plugin.rs b/crates/parcel_core/src/plugin/bundler_plugin.rs deleted file mode 100644 index 8de7779e905..00000000000 --- a/crates/parcel_core/src/plugin/bundler_plugin.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fmt::Debug; - -use crate::bundle_graph::BundleGraph; - -/// Converts an asset graph into a BundleGraph -/// -/// Bundlers accept the entire asset graph and modify it to add bundle nodes that group the assets -/// into output bundles. -/// -/// Bundle and optimize run in series and are functionally identitical. -/// -pub trait BundlerPlugin: Debug { - // TODO: Should BundleGraph be AssetGraph or something that contains AssetGraph in the name? - fn bundle(&self, bundle_graph: &mut BundleGraph) -> Result<(), anyhow::Error>; - - fn optimize(&self, bundle_graph: &mut BundleGraph) -> Result<(), anyhow::Error>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestBundlerPlugin {} - - impl BundlerPlugin for TestBundlerPlugin { - fn bundle(&self, _bundle_graph: &mut BundleGraph) -> Result<(), anyhow::Error> { - todo!() - } - - fn optimize(&self, _bundle_graph: &mut BundleGraph) -> Result<(), anyhow::Error> { - todo!() - } - } - - #[test] - fn can_be_dyn() { - let _bundler: Box = Box::new(TestBundlerPlugin {}); - } -} diff --git a/crates/parcel_core/src/plugin/compressor_plugin.rs b/crates/parcel_core/src/plugin/compressor_plugin.rs deleted file mode 100644 index 67cb71d2a98..00000000000 --- a/crates/parcel_core/src/plugin/compressor_plugin.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::fmt::Debug; -use std::fs::File; - -pub struct CompressedFile { - /// An optional file extension appended to the output file - /// - /// When no extension is returned, then the returned stream replaces the original file. - /// - pub extension: Option, - - /// The compressed file - pub file: File, -} - -/// Compresses the input file stream -pub trait CompressorPlugin: Debug { - /// Compress the given file - /// - /// The file contains the final contents of bundles and sourcemaps as they are being written. - /// A new stream can be returned, or None to forward compression onto the next plugin. - /// - fn compress(&self, file: &File) -> Result, String>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestCompressorPlugin {} - - impl CompressorPlugin for TestCompressorPlugin { - fn compress(&self, _file: &File) -> Result, String> { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut compressors = Vec::>::new(); - - compressors.push(Box::new(TestCompressorPlugin {})); - - assert_eq!(compressors.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/namer_plugin.rs b/crates/parcel_core/src/plugin/namer_plugin.rs deleted file mode 100644 index 7bea1362dba..00000000000 --- a/crates/parcel_core/src/plugin/namer_plugin.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::fmt::Debug; -use std::path::PathBuf; - -use crate::bundle_graph::BundleGraph; -use crate::types::Bundle; - -/// Determines the output filename for a bundle -/// -/// Namers run in a pipeline until one returns a result. -/// -pub trait NamerPlugin: Debug { - /// Names the given bundle - /// - /// The returned file path should be relative to the target dist directory, and will be used to - /// name the bundle. Naming can be forwarded onto the next plugin by returning None. - /// - fn name( - &self, - bundle: &Bundle, - bundle_graph: &BundleGraph, - ) -> Result, anyhow::Error>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestNamerPlugin {} - - impl NamerPlugin for TestNamerPlugin { - fn name( - &self, - _bundle: &Bundle, - _bundle_graph: &BundleGraph, - ) -> Result, anyhow::Error> { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut namers = Vec::>::new(); - - namers.push(Box::new(TestNamerPlugin {})); - - assert_eq!(namers.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/optimizer_plugin.rs b/crates/parcel_core/src/plugin/optimizer_plugin.rs deleted file mode 100644 index 90a1b64174b..00000000000 --- a/crates/parcel_core/src/plugin/optimizer_plugin.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt::Debug; -use std::fs::File; - -use crate::bundle_graph::BundleGraph; -use crate::types::Bundle; -use crate::types::SourceMap; - -pub struct OptimizeContext<'a> { - pub bundle: &'a Bundle, - pub bundle_graph: &'a BundleGraph, - pub contents: &'a File, // TODO We may want this to be a String or File later - pub map: Option<&'a SourceMap>, - // TODO getSourceMapReference? -} - -pub struct OptimizedBundle { - pub contents: File, - // TODO ast, map, type -} - -/// Optimises a bundle -/// -/// Optimizers are commonly used to implement minification, tree shaking, dead code elimination, -/// and other size reduction techniques that need a full bundle to be effective. However, -/// optimizers can also be used for any type of bundle transformation, such as prepending license -/// headers, converting inline bundles to base 64, etc. -/// -/// Multiple optimizer plugins may run in series, and the result of each optimizer is passed to -/// the next. -/// -pub trait OptimizerPlugin: Debug + Send + Sync { - /// Transforms the contents of a bundle and its source map - fn optimize(&self, ctx: OptimizeContext) -> Result; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestOptimizerPlugin {} - - impl OptimizerPlugin for TestOptimizerPlugin { - fn optimize(&self, _ctx: OptimizeContext) -> Result { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut optimizers = Vec::>::new(); - - optimizers.push(Box::new(TestOptimizerPlugin {})); - - assert_eq!(optimizers.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/packager_plugin.rs b/crates/parcel_core/src/plugin/packager_plugin.rs deleted file mode 100644 index 3b7d896d42a..00000000000 --- a/crates/parcel_core/src/plugin/packager_plugin.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::fmt::Debug; -use std::fs::File; - -use crate::bundle_graph::BundleGraph; -use crate::types::Bundle; -use crate::types::SourceMap; - -pub struct PackageContext<'a> { - pub bundle: &'a Bundle, - pub bundle_graph: &'a BundleGraph, - pub contents: &'a File, // TODO We may want this to be a String or File later - pub map: Option<&'a SourceMap>, - // TODO getSourceMapReference? -} - -pub struct PackagedBundle { - pub contents: File, - // TODO ast, map, type -} - -/// Combines all the assets in a bundle together into an output file -/// -/// Packagers are also responsible for resolving URL references, bundle inlining, and generating -/// source maps. -/// -pub trait PackagerPlugin: Debug + Send + Sync { - /// Combines assets in a bundle - fn package(&self, ctx: PackageContext) -> Result; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestPackagerPlugin {} - - impl PackagerPlugin for TestPackagerPlugin { - fn package(&self, _ctx: PackageContext) -> Result { - todo!() - } - } - - #[test] - fn can_be_dyn() { - let _packager: Box = Box::new(TestPackagerPlugin {}); - } -} diff --git a/crates/parcel_core/src/plugin/reporter_plugin.rs b/crates/parcel_core/src/plugin/reporter_plugin.rs deleted file mode 100644 index 8d6d9824bef..00000000000 --- a/crates/parcel_core/src/plugin/reporter_plugin.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::sync::Arc; -use std::{fmt::Debug, path::PathBuf}; - -use crate::types::Dependency; - -pub mod composite_reporter_plugin; - -pub struct ResolvingEvent { - pub dependency: Arc, -} - -pub struct AssetBuildEvent { - pub file_path: PathBuf, -} - -pub enum BuildProgressEvent { - Resolving(ResolvingEvent), - Building(AssetBuildEvent), -} - -// TODO Flesh these out -pub enum ReporterEvent { - BuildStart, - BuildProgress(BuildProgressEvent), - BuildFailure, - BuildSuccess, - Log, - Validation, - WatchStart, - WatchEnd, -} - -/// Receives events from Parcel as they occur throughout the build process -/// -/// For example, reporters may write status information to stdout, run a dev server, or generate a -/// bundle analysis report at the end of a build. -/// -#[mockall::automock] -pub trait ReporterPlugin: Debug + Send + Sync { - /// Processes the event from Parcel - fn report(&self, event: &ReporterEvent) -> Result<(), anyhow::Error>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestReporterPlugin {} - - impl ReporterPlugin for TestReporterPlugin { - fn report(&self, _event: &ReporterEvent) -> Result<(), anyhow::Error> { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut reporters = Vec::>::new(); - - reporters.push(Box::new(TestReporterPlugin {})); - - assert_eq!(reporters.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/reporter_plugin/composite_reporter_plugin.rs b/crates/parcel_core/src/plugin/reporter_plugin/composite_reporter_plugin.rs deleted file mode 100644 index bcc24ad1b37..00000000000 --- a/crates/parcel_core/src/plugin/reporter_plugin/composite_reporter_plugin.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; - -use crate::plugin::{ReporterEvent, ReporterPlugin}; - -#[cfg(not(test))] -type Reporter = Box; - -#[cfg(test)] -type Reporter = crate::plugin::MockReporterPlugin; - -/// A reporter plugin that delegates to multiple other reporters. -#[derive(Debug, Default)] -pub struct CompositeReporterPlugin { - reporters: Vec, -} - -impl CompositeReporterPlugin { - pub fn new(reporters: Vec) -> Self { - Self { reporters } - } -} - -#[derive(Debug)] -pub struct CompositeReporterPluginError { - errors: Vec, -} - -impl Display for CompositeReporterPluginError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "CompositeReporterPluginError {:?}", self.errors) - } -} - -impl std::error::Error for CompositeReporterPluginError {} - -impl ReporterPlugin for CompositeReporterPlugin { - /// Loop over reporters and call report on each one of them. - fn report(&self, event: &ReporterEvent) -> Result<(), anyhow::Error> { - let mut errors = vec![]; - for reporter in &self.reporters { - let result = reporter.report(event); - if let Err(error) = result { - errors.push(error) - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(anyhow::Error::new(CompositeReporterPluginError { errors })) - } - } -} - -#[cfg(test)] -mod test { - use crate::plugin::reporter_plugin::MockReporterPlugin; - use anyhow::anyhow; - - use super::*; - - #[test] - fn test_reporters_get_called() { - let mut reporter1 = MockReporterPlugin::new(); - let mut reporter2 = MockReporterPlugin::new(); - - reporter1.expect_report().times(1).returning(|_| Ok(())); - reporter2.expect_report().times(1).returning(|_| Ok(())); - - let composite_reporter = CompositeReporterPlugin::new(vec![reporter1, reporter2]); - - composite_reporter - .report(&ReporterEvent::BuildStart) - .unwrap(); - } - - #[test] - fn test_errors_are_forwarded_up() { - let mut reporter1 = MockReporterPlugin::new(); - let mut reporter2 = MockReporterPlugin::new(); - - reporter1 - .expect_report() - .times(1) - .returning(|_| Err(anyhow!("Failed"))); - reporter2.expect_report().times(1).returning(|_| Ok(())); - - let composite_reporter = CompositeReporterPlugin::new(vec![reporter1, reporter2]); - - let result = composite_reporter.report(&ReporterEvent::BuildStart); - assert!(result.is_err()); - assert!(result - .err() - .unwrap() - .to_string() - .starts_with("CompositeReporterPluginError [Failed")); - } -} diff --git a/crates/parcel_core/src/plugin/resolver_plugin.rs b/crates/parcel_core/src/plugin/resolver_plugin.rs deleted file mode 100644 index 6008de2b3fb..00000000000 --- a/crates/parcel_core/src/plugin/resolver_plugin.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::fmt::Debug; -use std::path::PathBuf; -use std::sync::Arc; - -use dyn_hash::DynHash; - -use crate::types::Dependency; -use crate::types::Invalidation; -use crate::types::JSONObject; -use crate::types::Priority; - -// TODO Diagnostics and invalidations - -pub struct ResolveContext { - pub dependency: Arc, - pub pipeline: Option, - pub specifier: String, -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct ResolvedResolution { - /// Whether this dependency can be deferred by Parcel itself - pub can_defer: bool, - - /// The code of the resolved asset - /// - /// If provided, this is used rather than reading the file from disk. - /// - pub code: Option, - - /// An absolute path to the resolved file - pub file_path: PathBuf, - - /// Is spread (shallowly merged) onto the request's dependency.meta - pub meta: Option, - - /// An optional named pipeline to compile the resolved file - pub pipeline: Option, - - /// Overrides the priority set on the dependency - pub priority: Option, - - /// Query parameters to be used by transformers when compiling the resolved file - pub query: Option, - - /// Corresponds to the asset side effects - pub side_effects: bool, -} - -#[derive(Debug, PartialEq)] -pub enum Resolution { - /// Indicates the dependency was not resolved - Unresolved, - - /// Whether the resolved file should be excluded from the build - Excluded, - - Resolved(ResolvedResolution), -} - -#[derive(Debug, PartialEq)] -pub struct Resolved { - pub invalidations: Vec, - pub resolution: Resolution, -} - -/// Converts a dependency specifier into a file path that will be processed by transformers -/// -/// Resolvers run in a pipeline until one of them return a result. -/// -pub trait ResolverPlugin: Debug + DynHash + Send + Sync { - /// Determines what the dependency specifier resolves to - fn resolve(&self, ctx: ResolveContext) -> Result; -} - -dyn_hash::hash_trait_object!(ResolverPlugin); - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug, Hash)] - struct TestResolverPlugin {} - - impl ResolverPlugin for TestResolverPlugin { - fn resolve(&self, _ctx: ResolveContext) -> Result { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut resolvers = Vec::>::new(); - - resolvers.push(Box::new(TestResolverPlugin {})); - - assert_eq!(resolvers.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/runtime_plugin.rs b/crates/parcel_core/src/plugin/runtime_plugin.rs deleted file mode 100644 index b056f18d070..00000000000 --- a/crates/parcel_core/src/plugin/runtime_plugin.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::fmt::Debug; -use std::path::PathBuf; - -use crate::bundle_graph::BundleGraph; -use crate::types::Bundle; -use crate::types::Dependency; - -pub enum RuntimeAssetPriority { - Sync, - Parallel, -} - -/// A "synthetic" asset that will be inserted into the bundle graph -pub struct RuntimeAsset { - pub code: String, - pub dependency: Option, - pub file_path: PathBuf, - pub is_entry: Option, - pub priority: Option, - // TODO env: Option -} - -/// Programmatically insert assets into bundles -pub trait RuntimePlugin: Debug { - /// Generates runtime assets to insert into bundles - fn apply( - &self, - bundle: Bundle, - bundle_graph: BundleGraph, - ) -> Result>, anyhow::Error>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestRuntimePlugin {} - - impl RuntimePlugin for TestRuntimePlugin { - fn apply( - &self, - _bundle: Bundle, - _bundle_graph: BundleGraph, - ) -> Result>, anyhow::Error> { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut runtimes = Vec::>::new(); - - runtimes.push(Box::new(TestRuntimePlugin {})); - - assert_eq!(runtimes.len(), 1); - } -} diff --git a/crates/parcel_core/src/plugin/transformer_plugin.rs b/crates/parcel_core/src/plugin/transformer_plugin.rs deleted file mode 100644 index ed2c06c555a..00000000000 --- a/crates/parcel_core/src/plugin/transformer_plugin.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::fmt::Debug; -use std::path::{Path, PathBuf}; -use std::sync::Arc; - -use serde::Serialize; - -use parcel_filesystem::FileSystemRef; - -use crate::types::{Asset, Code, Dependency, Environment, FileType, SpecifierType}; - -pub struct ResolveOptions { - /// A list of custom conditions to use when resolving package.json "exports" and "imports" - pub package_conditions: Vec, - /// How the specifier should be interpreted - pub specifier_type: SpecifierType, -} - -/// A function that enables transformers to resolve a dependency specifier -pub type Resolve = dyn Fn(PathBuf, String, ResolveOptions) -> Result; - -/// A newly resolved file_path/code that needs to be transformed into an Asset -#[derive(Default)] -pub struct InitialAsset { - pub file_path: PathBuf, - /// Dynamic code returned from the resolver for virtual files. - /// It is not set in most cases but should be respected when present. - pub code: Option, - pub env: Arc, - pub side_effects: bool, -} - -/// The input to transform within the plugin -/// -/// Transformers may run against two distinguished scenarios: -/// -/// * InitialAsset that have just been discovered -/// * Outputs of previous transformation steps, which are in-place modified -/// -pub enum TransformationInput { - InitialAsset(InitialAsset), - Asset(Asset), -} - -impl TransformationInput { - pub fn file_type(&self) -> FileType { - match self { - TransformationInput::InitialAsset(raw_asset) => FileType::from_extension( - raw_asset - .file_path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or_default(), - ), - TransformationInput::Asset(asset) => asset.file_type.clone(), - } - } - - pub fn env(&self) -> Arc { - match self { - TransformationInput::InitialAsset(raw_asset) => raw_asset.env.clone(), - TransformationInput::Asset(asset) => asset.env.clone(), - } - } - - pub fn file_path(&self) -> &Path { - match self { - TransformationInput::InitialAsset(raw_asset) => raw_asset.file_path.as_path(), - TransformationInput::Asset(asset) => &asset.file_path, - } - } - - pub fn read_code(&self, fs: FileSystemRef) -> anyhow::Result> { - match self { - TransformationInput::InitialAsset(raw_asset) => { - let code = if let Some(code) = &raw_asset.code { - Code::from(code.clone()) - } else { - let source = fs.read_to_string(&raw_asset.file_path)?; - Code::from(source) - }; - Ok(Arc::new(code)) - } - TransformationInput::Asset(asset) => Ok(asset.code.clone()), - } - } - - pub fn side_effects(&self) -> bool { - match self { - TransformationInput::InitialAsset(raw_asset) => raw_asset.side_effects, - TransformationInput::Asset(asset) => asset.side_effects, - } - } -} - -#[derive(Debug, Serialize, PartialEq)] -pub struct TransformResult { - pub asset: Asset, - pub dependencies: Vec, - /// The transformer signals through this field that its result should be invalidated - /// if these paths change. - pub invalidate_on_file_change: Vec, -} - -/// Compile a single asset, discover dependencies, or convert the asset to a different format -/// -/// Many transformers are wrappers around other tools such as compilers and preprocessors, and are -/// designed to integrate with Parcel. -/// -pub trait TransformerPlugin: Debug + Send + Sync { - /// Transform the asset and/or add new assets - fn transform(&mut self, input: TransformationInput) -> Result; -} diff --git a/crates/parcel_core/src/plugin/validator_plugin.rs b/crates/parcel_core/src/plugin/validator_plugin.rs deleted file mode 100644 index 4805cb02bd5..00000000000 --- a/crates/parcel_core/src/plugin/validator_plugin.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt::Debug; - -use super::ConfigLoader; -use crate::types::Asset; - -pub struct Validation { - pub errors: Vec, - pub warnings: Vec, -} - -/// Analyzes assets to ensure they are in a valid state -/// -/// Validators may throw errors or log warnings to indicate an asset is invalid. They can be used -/// to verify linting, type safety, etc and are run after a build has completed. This enables more -/// important compilation errors to occur first. -/// -/// When Parcel runs in watch mode, the built bundles are served even if a validator throws an -/// error. But when running a build, Parcel exits with a failure and status code to ensure code is -/// not deployed for assets that do not meet the validation criteria. This ensures developers -/// remain productive, and do not have to worry about every small typing or linting issue while -/// trying to solve a problem. -/// -pub trait ValidatorPlugin: Debug { - /// A hook designed to setup config needed to validate assets - /// - /// This function will run once, shortly after the plugin is initialised. - /// - fn load_config(&mut self, config: &ConfigLoader) -> Result<(), anyhow::Error>; - - /// Validates a single asset at a time - /// - /// This is usually designed for stateless validators - /// - fn validate_asset( - &mut self, - config: &ConfigLoader, - asset: &Asset, - ) -> Result; - - /// Validates all assets - /// - /// Some validators may wish to maintain a project-wide state or cache for efficiency. For these - /// cases, it is appropriate to use a different interface where Parcel passses all the changed - /// files to the validator at the same time. - /// - /// This type of validator is slower than a stateless validator, as it runs everything on a - /// single thread. Only use this if you have no other choice, as is typically the case for - /// validators that need to have access to the entire project, like TypeScript. - /// - fn validate_assets( - &mut self, - config: &ConfigLoader, - assets: Vec<&Asset>, - ) -> Result; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestValidatorPlugin {} - - impl ValidatorPlugin for TestValidatorPlugin { - fn load_config(&mut self, _config: &ConfigLoader) -> Result<(), anyhow::Error> { - todo!() - } - - fn validate_asset( - &mut self, - _config: &ConfigLoader, - _asset: &Asset, - ) -> Result { - todo!() - } - - fn validate_assets( - &mut self, - _config: &ConfigLoader, - _assets: Vec<&Asset>, - ) -> Result { - todo!() - } - } - - #[test] - fn can_be_defined_in_dyn_vec() { - let mut validators = Vec::>::new(); - - validators.push(Box::new(TestValidatorPlugin {})); - - assert_eq!(validators.len(), 1); - } -} diff --git a/crates/parcel_core/src/types.rs b/crates/parcel_core/src/types.rs deleted file mode 100644 index 6be1d7b9746..00000000000 --- a/crates/parcel_core/src/types.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Re-export this from core, probably want to move this type here -pub use parcel_filesystem::FileSystem; - -pub use self::asset::*; -pub use self::bundle::*; -pub use self::dependency::*; -pub use self::diagnostic::*; -pub use self::environment::*; -pub use self::file::*; -pub use self::file_type::*; -pub use self::invalidation::*; -pub use self::json::*; -pub use self::package_json::*; -pub use self::parcel_options::*; -pub use self::source::*; -pub use self::symbol::*; -pub use self::target::*; - -mod asset; -mod bundle; -mod dependency; -mod diagnostic; -mod environment; -mod file; -mod file_type; -mod invalidation; -mod json; -mod package_json; -mod parcel_options; -mod source; -mod symbol; -mod target; -mod utils; diff --git a/crates/parcel_core/src/types/asset.rs b/crates/parcel_core/src/types/asset.rs deleted file mode 100644 index 6d143b7313b..00000000000 --- a/crates/parcel_core/src/types/asset.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroU32; -use std::path::PathBuf; -use std::sync::Arc; - -use serde::Deserialize; -use serde::Serialize; - -use super::bundle::BundleBehavior; -use super::environment::Environment; -use super::file_type::FileType; -use super::json::JSONObject; -use super::symbol::Symbol; - -#[derive(PartialEq, Hash, Clone, Copy, Debug)] -pub struct AssetId(pub NonZeroU32); - -/// The source code for an asset. -#[derive(PartialEq, Default, Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase", transparent)] -pub struct Code { - inner: String, -} - -impl Code { - pub fn bytes(&self) -> &[u8] { - self.inner.as_bytes() - } - - pub fn size(&self) -> u32 { - self.inner.len() as u32 - } -} - -impl Display for Code { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner) - } -} - -impl From for Code { - fn from(value: String) -> Self { - Self { inner: value } - } -} - -/// An asset is a file or part of a file that may represent any data type including source code, binary data, etc. -/// -/// Note that assets may exist in the file system or virtually. -/// -#[derive(Default, PartialEq, Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Asset { - /// Controls which bundle the asset is placed into - pub bundle_behavior: BundleBehavior, - - /// The environment of the asset - pub env: Arc, - - /// The file path to the asset - pub file_path: PathBuf, - - /// The file type of the asset, which may change during transformation - #[serde(rename = "type")] - pub file_type: FileType, - - /// The code of this asset, initially read from disk, then becoming the - /// transformed output - pub code: Arc, - - /// Plugin specific metadata for the asset - pub meta: JSONObject, - - /// The pipeline defined in .parcelrc that the asset should be processed with - pub pipeline: Option, - - /// The transformer options for the asset from the dependency query string - pub query: Option, - - /// Statistics about the asset - pub stats: AssetStats, - - /// The symbols that the asset exports - pub symbols: Vec, - - /// A unique key that identifies an asset - /// - /// When a transformer returns multiple assets, it can give them unique keys to identify them. - /// This can be used to find assets during packaging, or to create dependencies between multiple - /// assets returned by a transformer by using the unique key as the dependency specifier. - /// - /// TODO: Make this non-nullable and disallow creating assets without it. - pub unique_key: Option, - - /// Whether this asset can be omitted if none of its exports are being used - /// - /// This is initially set by the resolver, but can be overridden by transformers. - /// - pub side_effects: bool, - - /// Indicates if the asset is used as a bundle entry - /// - /// This controls whether a bundle can be split into multiple, or whether all of the - /// dependencies must be placed in a single bundle. - /// - pub is_bundle_splittable: bool, - - /// Whether this asset is part of the project, and not an external dependency - /// - /// This indicates that transformation using the project configuration should be applied. - /// - pub is_source: bool, - - /// True if the asset has CommonJS exports - pub has_cjs_exports: bool, - - /// This is true unless the module is a CommonJS module that does non-static access of the - /// `this`, `exports` or `module.exports` objects. For example if the module uses code like - /// `module.exports[key] = 10`. - pub static_exports: bool, - - /// TODO: MISSING DOCUMENTATION - pub should_wrap: bool, - - /// TODO: MISSING DOCUMENTATION - pub has_node_replacements: bool, - - /// True if this is a 'constant module', meaning it only exports constant assignment statements, - /// on this case this module may be inlined on its usage depending on whether it is only used - /// once and the parcel configuration. - /// - /// An example of a 'constant module' would be: - /// - /// ```skip - /// export const MY_CONSTANT = 'some-value'; - /// ``` - pub is_constant_module: bool, - - /// True if `Asset::symbols` has been populated. This field is deprecated and should be phased - /// out. - pub has_symbols: bool, -} - -impl Asset { - pub fn id(&self) -> u64 { - let mut hasher = crate::hash::IdentifierHasher::default(); - - self.env.hash(&mut hasher); - self.file_path.hash(&mut hasher); - self.file_type.hash(&mut hasher); - self.pipeline.hash(&mut hasher); - self.query.hash(&mut hasher); - self.unique_key.hash(&mut hasher); - - hasher.finish() - } - - pub fn set_interpreter(&mut self, shebang: impl Into) { - self.meta.insert("interpreter".into(), shebang.into()); - } - - pub fn set_has_cjs_exports(&mut self, value: bool) { - self.meta.insert("hasCJSExports".into(), value.into()); - self.has_cjs_exports = value; - } - - pub fn set_static_exports(&mut self, value: bool) { - self.meta.insert("staticExports".into(), value.into()); - self.static_exports = value; - } - - pub fn set_should_wrap(&mut self, value: bool) { - self.meta.insert("shouldWrap".into(), value.into()); - self.should_wrap = value; - } - pub fn set_is_constant_module(&mut self, is_constant_module: bool) { - self.is_constant_module = is_constant_module; - if is_constant_module { - self.meta.insert("isConstantModule".into(), true.into()); - } - } - - pub fn set_has_node_replacements(&mut self, has_node_replacements: bool) { - self.has_node_replacements = has_node_replacements; - if has_node_replacements { - self - .meta - // This is intentionally snake_case as that's what it was originally. - .insert("has_node_replacements".into(), true.into()); - } - } -} - -/// Statistics that pertain to an asset -#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)] -pub struct AssetStats { - pub size: u32, - pub time: u32, -} diff --git a/crates/parcel_core/src/types/bundle.rs b/crates/parcel_core/src/types/bundle.rs deleted file mode 100644 index ccbd37740ef..00000000000 --- a/crates/parcel_core/src/types/bundle.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; -use serde_repr::Deserialize_repr; -use serde_repr::Serialize_repr; - -use super::environment::Environment; -use super::file_type::FileType; -use super::target::Target; - -#[derive(Clone, Debug, Deserialize, Hash, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Bundle { - /// Controls the behavior of the bundle to determine when the bundle loads - pub bundle_behavior: BundleBehavior, - - /// The type of the bundle - #[serde(rename = "type")] - pub bundle_type: FileType, - - /// The list of assets executed immediately when the bundle is loaded - /// - /// Some bundles may not have any entry assets, like shared bundles. - /// - pub entry_asset_ids: Vec, - - /// The environment of the bundle - pub env: Environment, - - /// A placeholder for the bundle content hash - /// - /// It can be used in the bundle's name or the contents of another bundle. Hash references are replaced - /// with a content hash of the bundle after packaging and optimizing. - /// - pub hash_reference: String, - - /// The bundle id - pub id: String, - - /// Whether the bundle can be split - /// - /// If false, then all dependencies of the bundle will be kept internal to the bundle, rather - /// than referring to other bundles. This may result in assets being duplicated between - /// multiple bundles, but can be useful for things like server side rendering. - /// - pub is_splittable: bool, - - /// The main entry of the bundle, which will provide the bundle exports - /// - /// Some bundles, such as shared bundles, may not have a main entry. - /// - pub main_entry_id: Option, - - pub manual_shared_bundle: Option, - - /// The name of the bundle, which is a file path relative to the bundle target directory - /// - /// The bundle name may include a hash reference, but not the final content hash. - /// - pub name: Option, - - /// Indicates that the name should be stable over time, even when the content of the bundle changes - pub needs_stable_name: bool, - - /// The pipeline associated with the bundle - pub pipeline: Option, - - /// A shortened version of the bundle id that is used to refer to the bundle at runtime - pub public_id: Option, - - /// The output target for the bundle - pub target: Target, -} - -/// Determines when the bundle loads -#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)] -#[repr(u8)] -pub enum BundleBehavior { - /// Embeds an asset into the parent bundle by creating an inline bundle - Inline = 0, - - /// The asset will be isolated from its parents in a separate bundle, and shared assets will be duplicated - Isolated = 1, - - /// Unspecified bundling behavior - None = 255, -} - -impl Default for BundleBehavior { - fn default() -> Self { - BundleBehavior::None - } -} diff --git a/crates/parcel_core/src/types/bundle_graph.rs b/crates/parcel_core/src/types/bundle_graph.rs deleted file mode 100644 index 0dccafb6497..00000000000 --- a/crates/parcel_core/src/types/bundle_graph.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct BundleGraph {} diff --git a/crates/parcel_core/src/types/dependency.rs b/crates/parcel_core/src/types/dependency.rs deleted file mode 100644 index f850ea20c91..00000000000 --- a/crates/parcel_core/src/types/dependency.rs +++ /dev/null @@ -1,266 +0,0 @@ -use core::panic; -use std::hash::Hash; -use std::hash::Hasher; -use std::path::PathBuf; -use std::sync::Arc; - -use serde::Deserialize; -use serde::Serialize; -use serde_repr::Deserialize_repr; -use serde_repr::Serialize_repr; - -use crate::types::ExportsCondition; - -use super::bundle::BundleBehavior; -use super::environment::Environment; -use super::json::JSONObject; -use super::source::SourceLocation; -use super::symbol::Symbol; -use super::target::Target; - -/// A dependency denotes a connection between two assets -#[derive(PartialEq, Clone, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Dependency { - /// Controls the behavior of the bundle the resolved asset is placed into - /// - /// This option is used in combination with priority to determine when the bundle is loaded. - /// - pub bundle_behavior: BundleBehavior, - - /// The environment of the dependency - pub env: Arc, - - /// The location within the source file where the dependency was found - #[serde(default)] - pub loc: Option, - - /// Plugin-specific metadata for the dependency - #[serde(default)] - pub meta: JSONObject, - - /// A list of custom conditions to use when resolving package.json "exports" and "imports" - /// - /// This will be combined with the conditions from the environment. However, it overrides the default "import" and "require" conditions inferred from the specifierType. To include those in addition to custom conditions, explicitly add them to this list. - /// - #[serde(default)] - pub package_conditions: ExportsCondition, - - /// The pipeline defined in .parcelrc that the dependency should be processed with - #[serde(default)] - pub pipeline: Option, - - /// Determines when the dependency should be loaded - pub priority: Priority, - - /// The semver version range expected for the dependency - pub range: Option, - - /// The file path where the dependency should be resolved from - /// - /// By default, this is the path of the source file where the dependency was specified. - /// - pub resolve_from: Option, - - /// The id of the asset with this dependency - pub source_asset_id: Option, - - /// The file path of the asset with this dependency - pub source_path: Option, - - /// The import or export specifier that connects two assets together - pub specifier: String, - - /// How the specifier should be interpreted - pub specifier_type: SpecifierType, - - /// These are the "Symbols" this dependency has which are used in import sites. - /// - /// We might want to split this information from this type. - #[serde(default)] - pub symbols: Vec, - - /// The target associated with an entry, if any - #[serde(default)] - pub target: Option>, - - /// Whether the dependency is an entry - pub is_entry: bool, - - /// Whether the dependency is optional - /// - /// If an optional dependency cannot be resolved, it will not fail the build. - /// - pub is_optional: bool, - - /// Indicates that the name should be stable over time, even when the content of the bundle changes - /// - /// When the dependency is a bundle entry (priority is "parallel" or "lazy"), this controls the - /// naming of that bundle. - /// - /// This is useful for entries that a user would manually enter the URL for, as well as for - /// things like service workers or RSS feeds, where the URL must remain consistent over time. - /// - pub needs_stable_name: bool, - - pub should_wrap: bool, - - /// Whether this dependency object corresponds to an ESM import/export statement or to a dynamic - /// import expression. - pub is_esm: bool, - - /// Whether the symbols vector of this dependency has had symbols added to it. - pub has_symbols: bool, - - pub placeholder: Option, -} - -impl Dependency { - pub fn entry(entry: String, target: Target) -> Dependency { - let is_library = target.env.is_library; - let mut symbols = Vec::new(); - - if is_library { - symbols.push(Symbol { - exported: "*".into(), - is_esm_export: false, - is_weak: true, - loc: None, - local: "*".into(), - self_referenced: false, - }); - } - - Dependency { - env: target.env.clone(), - has_symbols: is_library, - is_entry: true, - needs_stable_name: true, - specifier: entry, - specifier_type: SpecifierType::Url, - symbols, - target: Some(Box::new(target)), - ..Dependency::default() - } - } - - pub fn new(specifier: String, env: Arc) -> Dependency { - Dependency { - env, - meta: JSONObject::new(), - specifier, - ..Dependency::default() - } - } - - pub fn id(&self) -> u64 { - let mut hasher = crate::hash::IdentifierHasher::default(); - self.hash(&mut hasher); - hasher.finish() - } - - pub fn set_placeholder(&mut self, placeholder: impl Into) { - self.meta.insert("placeholder".into(), placeholder.into()); - } - - pub fn set_is_webworker(&mut self) { - self.meta.insert("webworker".into(), true.into()); - } - - pub fn set_kind(&mut self, kind: impl Into) { - self.meta.insert("kind".into(), kind.into()); - } - - pub fn set_should_wrap(&mut self, should_wrap: bool) { - self.meta.insert("shouldWrap".into(), should_wrap.into()); - self.should_wrap = should_wrap; - } - - pub fn set_promise_symbol(&mut self, name: impl Into) { - self.meta.insert("promiseSymbol".into(), name.into()); - } - - pub fn set_add_import_attibute(&mut self, attribute: impl Into) { - let object = self - .meta - .entry(String::from("importAttributes")) - .or_insert(serde_json::Map::new().into()); - - if let serde_json::Value::Object(import_attributes) = object { - import_attributes.insert(attribute.into(), true.into()); - } else { - panic!("Dependency import attributes invalid. This should never happen"); - } - } -} - -impl Hash for Dependency { - fn hash(&self, state: &mut H) { - self.bundle_behavior.hash(state); - self.env.hash(state); - self.package_conditions.hash(state); - self.pipeline.hash(state); - self.priority.hash(state); - self.source_path.hash(state); - self.specifier.hash(state); - self.specifier_type.hash(state); - } -} - -#[derive(Clone, Debug, Deserialize, Hash, Serialize)] -pub struct ImportAttribute { - pub key: String, - pub value: bool, -} - -/// Determines when a dependency should load -#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)] -#[serde(rename_all = "lowercase")] -#[repr(u8)] -pub enum Priority { - /// Resolves the dependency synchronously, placing the resolved asset in the same bundle as the parent or another bundle that is already on the page - Sync = 0, - /// Places the dependency in a separate bundle loaded in parallel with the current bundle - Parallel = 1, - /// The dependency should be placed in a separate bundle that is loaded later - Lazy = 2, -} - -impl Default for Priority { - fn default() -> Self { - Priority::Sync - } -} - -/// The type of the import specifier -#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)] -#[serde(rename_all = "lowercase")] -#[repr(u8)] -pub enum SpecifierType { - /// An ES Module specifier - /// - /// This is parsed as an URL, but bare specifiers are treated as node_modules. - /// - Esm = 0, - - /// A CommonJS specifier - /// - /// This is not parsed as an URL. - /// - CommonJS = 1, - - /// A URL that works as in a browser - /// - /// Bare specifiers are treated as relative URLs. - /// - Url = 2, - - /// A custom specifier that must be handled by a custom resolver plugin - Custom = 3, -} - -impl Default for SpecifierType { - fn default() -> Self { - SpecifierType::Esm - } -} diff --git a/crates/parcel_core/src/types/diagnostic.rs b/crates/parcel_core/src/types/diagnostic.rs deleted file mode 100644 index 711c61c1810..00000000000 --- a/crates/parcel_core/src/types/diagnostic.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::{ - config_loader::ConfigFile, - types::{FileType, Location, SourceLocation}, -}; -use derive_builder::Builder; -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Display, Formatter}, - path::PathBuf, -}; - -use super::File; - -/// Represents the kind of diagnostic -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub enum ErrorKind { - NotFound, - #[default] - Unknown, -} - -/// This is a user facing error for Parcel. -/// -/// Usually but not always this is linked to a source-code location. -#[derive(Builder, Debug, Deserialize, PartialEq, Serialize)] -#[builder(derive(Debug))] -#[serde(rename_all = "camelCase")] -pub struct Diagnostic { - #[builder(default)] - pub kind: ErrorKind, - - /// A list of files with source-code highlights - #[builder(default)] - pub code_frames: Vec, - - /// URL for the user to refer to documentation - #[builder(default)] - #[serde(rename = "documentationURL")] - pub documentation_url: Option, - - /// Hints for the user - #[builder(default)] - pub hints: Vec, - - /// A summary user-facing message - #[builder(setter(into))] - pub message: String, - - /// Indicates where this diagnostic was emitted from - /// - /// Consumers can also enable backtraces for more detailed origin information. - #[builder(default)] - pub origin: Option, -} - -impl Display for Diagnostic { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct Language(FileType); - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CodeFrame { - /// Source-code of the file at the time of error - // TODO: might want to RC or intern - pub code: Option, - - /// List of source-code highlight messages - pub code_highlights: Vec, - - /// The language associated with the code - pub language: Option, - - /// Path to the source file if applicable. - // TODO: In the future we might need to discern between errors on a source file in disk or in-memory. - pub path: Option, -} - -impl From for CodeFrame { - fn from(file: File) -> Self { - let language = file - .path - .extension() - .map(|ext| Language(FileType::from_extension(&ext.to_string_lossy()))); - - CodeFrame { - code: Some(file.contents), - code_highlights: Vec::new(), - language, - path: Some(file.path), - } - } -} - -impl From<&ConfigFile> for CodeFrame { - fn from(file: &ConfigFile) -> Self { - CodeFrame::from(File { - contents: file.raw.clone(), - path: file.path.clone(), - }) - } -} - -impl From for CodeFrame { - fn from(path: PathBuf) -> Self { - let language = path - .extension() - .map(|ext| Language(FileType::from_extension(&ext.to_string_lossy()))); - - CodeFrame { - code: None, - code_highlights: Vec::new(), - language, - path: Some(path), - } - } -} - -/// Represents a snippet of code to highlight -#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Clone)] -pub struct CodeHighlight { - /// An optional message to display around the source-code range - pub message: Option, - - /// The start location to highlight - pub start: Location, - - /// The end location to highlight - pub end: Location, -} - -impl From<[usize; 2]> for CodeHighlight { - fn from(loc: [usize; 2]) -> Self { - CodeHighlight { - message: None, - start: Location { - line: loc[0], - column: loc[1], - }, - end: Location { - line: loc[0] + 1, - column: 1, - }, - } - } -} - -impl From for CodeHighlight { - fn from(loc: SourceLocation) -> Self { - CodeHighlight { - message: None, - start: loc.start.clone(), - end: Location { - line: loc.end.line, - column: loc.end.column - 1, - }, - } - } -} - -pub type DiagnosticError = anyhow::Error; - -#[doc(hidden)] -pub mod __diagnostic { - #[doc(hidden)] - pub use anyhow::anyhow; -} - -#[macro_export] -macro_rules! diagnostic { - ($fmt:expr, $($arg:tt)*) => { - $crate::types::DiagnosticBuilder::default() - .message(format!($fmt, $($arg)*)) - .origin(Some(module_path!().to_string())) - .build() - .unwrap() - }; - ($msg:literal $(,)?) => { - $crate::types::DiagnosticBuilder::default() - .message($msg) - .origin(Some(module_path!().to_string())) - .build() - .unwrap() - }; - ($diagnostic:expr) => { - $diagnostic - .origin(Some(module_path!().to_string())) - .build() - .unwrap() - }; -} - -#[macro_export] -macro_rules! diagnostic_error { - ($fmt:expr, $($arg:tt)*) => { - $crate::types::__diagnostic::anyhow!($crate::diagnostic!($fmt, $($arg)*)) - }; - ($msg:literal $(,)?) => { - $crate::types::__diagnostic::anyhow!($crate::diagnostic!($msg)) - }; - ($diagnostic:expr) => { - $crate::types::__diagnostic::anyhow!($crate::diagnostic!($diagnostic)) - }; -} diff --git a/crates/parcel_core/src/types/environment.rs b/crates/parcel_core/src/types/environment.rs deleted file mode 100644 index 1e7b206cde2..00000000000 --- a/crates/parcel_core/src/types/environment.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::hash::Hasher; -use std::num::NonZeroU32; - -use serde::Deserialize; -use serde::Serialize; -use serde_repr::Deserialize_repr; -use serde_repr::Serialize_repr; - -use self::engines::Engines; -use super::source::SourceLocation; - -pub mod browsers; -pub mod engines; -mod output_format; -pub mod version; - -pub use output_format::OutputFormat; - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct EnvironmentId(pub NonZeroU32); - -/// The environment the built code will run in -/// -/// This influences how Parcel compiles your code, including what syntax to transpile. -/// -#[derive(Clone, Debug, Default, Deserialize, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Environment { - /// The environment the output should run in - pub context: EnvironmentContext, - - /// The engines supported by the environment - pub engines: Engines, - - /// Describes which node_modules should be included in the output - pub include_node_modules: IncludeNodeModules, - - /// Whether this is a library build - /// - /// Treats the target as a library that would be published to npm and consumed by another tool, - /// rather than used directly in a browser or other target environment. - /// - /// Library targets must enable scope hoisting, and use a non-global output format. - /// - pub is_library: bool, - - pub loc: Option, - - /// Determines what type of module to output - pub output_format: OutputFormat, - - /// Determines whether scope hoisting should be enabled - /// - /// By default, scope hoisting is enabled for production builds. - /// - pub should_scope_hoist: bool, - - /// Determines whether the output should be optimised - /// - /// The exact behavior of this flag is determined by plugins. By default, optimization is - /// enabled during production builds for application targets. - /// - pub should_optimize: bool, - - /// Configures source maps, which are enabled by default - pub source_map: Option, - - pub source_type: SourceType, -} - -impl Hash for Environment { - fn hash(&self, state: &mut H) { - // Hashing intentionally does not include loc - self.context.hash(state); - self.engines.hash(state); - self.include_node_modules.hash(state); - self.is_library.hash(state); - self.output_format.hash(state); - self.should_scope_hoist.hash(state); - self.should_optimize.hash(state); - self.source_map.hash(state); - self.source_type.hash(state); - } -} - -impl PartialEq for Environment { - fn eq(&self, other: &Self) -> bool { - // Equality intentionally does not include loc - self.context == other.context - && self.engines == other.engines - && self.include_node_modules == other.include_node_modules - && self.is_library == other.is_library - && self.output_format == other.output_format - && self.should_scope_hoist == other.should_scope_hoist - && self.should_optimize == other.should_optimize - && self.source_map == other.source_map - && self.source_type == other.source_type - } -} - -/// The environment the output should run in -/// -/// This informs Parcel what environment-specific APIs are available. -/// -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum EnvironmentContext { - #[default] - Browser, - ElectronMain, - ElectronRenderer, - Node, - ServiceWorker, - WebWorker, - Worklet, -} - -impl EnvironmentContext { - pub fn is_node(&self) -> bool { - use EnvironmentContext::*; - matches!(self, Node | ElectronMain | ElectronRenderer) - } - - pub fn is_browser(&self) -> bool { - use EnvironmentContext::*; - matches!( - self, - Browser | WebWorker | ServiceWorker | Worklet | ElectronRenderer - ) - } - - pub fn is_worker(&self) -> bool { - use EnvironmentContext::*; - matches!(self, WebWorker | ServiceWorker) - } - - pub fn is_worklet(&self) -> bool { - use EnvironmentContext::*; - matches!(self, Worklet) - } - - pub fn is_electron(&self) -> bool { - use EnvironmentContext::*; - matches!(self, ElectronMain | ElectronRenderer) - } -} - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(untagged)] -pub enum IncludeNodeModules { - Bool(bool), - Array(Vec), - Map(HashMap), -} - -impl Default for IncludeNodeModules { - fn default() -> Self { - IncludeNodeModules::Bool(true) - } -} - -impl From for IncludeNodeModules { - fn from(context: EnvironmentContext) -> Self { - match context { - EnvironmentContext::Browser => IncludeNodeModules::Bool(true), - EnvironmentContext::ServiceWorker => IncludeNodeModules::Bool(true), - EnvironmentContext::WebWorker => IncludeNodeModules::Bool(true), - _ => IncludeNodeModules::Bool(false), - } - } -} - -impl Hash for IncludeNodeModules { - fn hash(&self, state: &mut H) { - match self { - IncludeNodeModules::Bool(b) => b.hash(state), - IncludeNodeModules::Array(a) => a.hash(state), - IncludeNodeModules::Map(m) => { - for (k, v) in m { - k.hash(state); - v.hash(state); - } - } - } - } -} - -#[derive(Clone, Copy, Debug, Default, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)] -#[repr(u8)] -pub enum SourceType { - #[default] - Module = 0, - Script = 1, -} - -/// Source map options for the target output -#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TargetSourceMapOptions { - /// Inlines the source map as a data URL into the bundle, rather than link to it as a separate output file - inline: Option, - - /// Inlines the original source code into the source map, rather than loading them from the source root - /// - /// This is set to true by default when building browser targets for production. - /// - inline_sources: Option, - - /// The URL to load the original source code from - /// - /// This is set automatically in development when using the builtin Parcel development server. - /// Otherwise, it defaults to a relative path to the bundle from the project root. - /// - source_root: Option, -} diff --git a/crates/parcel_core/src/types/environment/browsers.rs b/crates/parcel_core/src/types/environment/browsers.rs deleted file mode 100644 index c42a8311830..00000000000 --- a/crates/parcel_core/src/types/environment/browsers.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::fmt::Display; -use std::fmt::Formatter; - -use browserslist::Distrib; - -use super::version::Version; - -/// List of targeted browsers -#[derive(Clone, Default, Debug, Eq, Hash, PartialEq)] -pub struct Browsers { - pub android: Option, - pub chrome: Option, - pub edge: Option, - pub firefox: Option, - pub ie: Option, - pub ios_saf: Option, - pub opera: Option, - pub safari: Option, - pub samsung: Option, -} - -impl Browsers { - pub fn is_empty(&self) -> bool { - self.iter().all(|(_browser, version)| version.is_none()) - } - - pub fn iter(&self) -> BrowsersIterator { - BrowsersIterator { - browsers: self, - index: 0, - } - } -} - -pub struct BrowsersIterator<'a> { - browsers: &'a Browsers, - index: usize, -} - -impl<'a> Iterator for BrowsersIterator<'a> { - type Item = (&'a str, Option); - - fn next(&mut self) -> Option { - let browser = match self.index { - 0 => Some(("android", self.browsers.android)), - 1 => Some(("chrome", self.browsers.chrome)), - 2 => Some(("edge", self.browsers.edge)), - 3 => Some(("firefox", self.browsers.firefox)), - 4 => Some(("ie", self.browsers.ie)), - 5 => Some(("ios_saf", self.browsers.ios_saf)), - 6 => Some(("opera", self.browsers.opera)), - 7 => Some(("safari", self.browsers.safari)), - 8 => Some(("samsung", self.browsers.samsung)), - _ => None, - }; - - if self.index < 9 { - self.index += 1; - } - - browser - } -} - -impl Display for Browsers { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - macro_rules! browsers { - ( $( $b:ident ),* ) => { - // Bypass unused_assignments false positive - let mut _is_first_write = true; - $( - if let Some(version) = self.$b { - if !_is_first_write { - write!(f, ", ")?; - } - _is_first_write = false; - write!(f, "{} {}", stringify!($b), version)?; - } - )* - }; - } - - browsers![android, chrome, edge, firefox, ie, ios_saf, opera, safari, samsung]; - Ok(()) - } -} - -impl From> for Browsers { - fn from(distribs: Vec) -> Self { - let mut browsers = Browsers::default(); - for distrib in distribs { - macro_rules! browser { - ($browser: ident) => {{ - if let Ok(v) = distrib.version().parse() { - if browsers.$browser.is_none() || v < browsers.$browser.unwrap() { - browsers.$browser = Some(v); - } - } - }}; - } - - match distrib.name() { - "android" => browser!(android), - "chrome" | "and_chr" => browser!(chrome), - "edge" => browser!(edge), - "firefox" | "and_ff" => browser!(firefox), - "ie" => browser!(ie), - "ios_saf" => browser!(ios_saf), - "opera" | "op_mob" => browser!(opera), - "safari" => browser!(safari), - "samsung" => browser!(samsung), - _ => {} - } - } - - browsers - } -} - -impl<'de> serde::Deserialize<'de> for Browsers { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = serde_value::Value::deserialize(deserializer)?; - let browsers = match value { - serde_value::Value::String(s) => vec![s], - value => Vec::::deserialize(serde_value::ValueDeserializer::new(value))?, - }; - let distribs = browserslist::resolve(browsers, &Default::default()).unwrap_or(Vec::new()); - Ok(distribs.into()) - } -} - -impl serde::Serialize for Browsers { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - format!("{}", self).serialize(serializer) - } -} - -#[cfg(test)] -mod tests { - use std::num::NonZeroU16; - - use super::*; - - #[test] - fn display() { - assert_eq!(format!("{}", Browsers::default()), ""); - - assert_eq!( - format!( - "{}", - Browsers { - chrome: Some(Version::new(NonZeroU16::new(100).unwrap(), 0)), - ..Browsers::default() - } - ), - "chrome >= 100" - ); - - assert_eq!( - format!( - "{}", - Browsers { - chrome: Some(Version::new(NonZeroU16::new(1).unwrap(), 20)), - ..Browsers::default() - } - ), - "chrome >= 1.20" - ); - - assert_eq!( - format!( - "{}", - Browsers { - chrome: Some(Version::new(NonZeroU16::new(1).unwrap(), 20)), - firefox: Some(Version::new(NonZeroU16::new(100).unwrap(), 5)), - ..Browsers::default() - } - ), - "chrome >= 1.20, firefox >= 100.5" - ); - } -} diff --git a/crates/parcel_core/src/types/environment/engines.rs b/crates/parcel_core/src/types/environment/engines.rs deleted file mode 100644 index a755764e121..00000000000 --- a/crates/parcel_core/src/types/environment/engines.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::num::NonZeroU16; - -use serde::Deserialize; -use serde::Serialize; - -use super::browsers::Browsers; -use super::version::Version; -use super::OutputFormat; - -/// The engines field in package.json -#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Engines { - #[serde(default)] - pub browsers: Browsers, - pub electron: Option, - pub node: Option, - pub parcel: Option, -} - -/// List of environment features that may be supported by an engine -pub enum EnvironmentFeature { - ArrowFunctions, - DynamicImport, - Esmodules, - GlobalThis, - ImportMetaUrl, - ServiceWorkerModule, - WorkerModule, -} - -impl EnvironmentFeature { - pub fn engines(&self) -> Engines { - match self { - EnvironmentFeature::WorkerModule => Engines { - browsers: Browsers { - edge: Some(Version::new(NonZeroU16::new(80).unwrap(), 0)), - chrome: Some(Version::new(NonZeroU16::new(80).unwrap(), 0)), - opera: Some(Version::new(NonZeroU16::new(67).unwrap(), 0)), - android: Some(Version::new(NonZeroU16::new(81).unwrap(), 0)), - ..Default::default() - }, - ..Default::default() - }, - EnvironmentFeature::DynamicImport => Engines { - browsers: Browsers { - edge: Some(Version::new(NonZeroU16::new(76).unwrap(), 0)), - firefox: Some(Version::new(NonZeroU16::new(67).unwrap(), 0)), - chrome: Some(Version::new(NonZeroU16::new(63).unwrap(), 0)), - safari: Some(Version::new(NonZeroU16::new(11).unwrap(), 1)), - opera: Some(Version::new(NonZeroU16::new(50).unwrap(), 0)), - ios_saf: Some(Version::new(NonZeroU16::new(11).unwrap(), 3)), - android: Some(Version::new(NonZeroU16::new(63).unwrap(), 0)), - samsung: Some(Version::new(NonZeroU16::new(8).unwrap(), 0)), - ..Default::default() - }, - ..Default::default() - }, - _ => todo!(), - } - } -} - -/// List of browsers to exclude when the esmodule target is specified based on -/// https://caniuse.com/#feat=es6-module -const _ESMODULE_BROWSERS: &'static [&'static str] = &[ - "not ie <= 11", - "not edge < 16", - "not firefox < 60", - "not chrome < 61", - "not safari < 11", - "not opera < 48", - "not ios_saf < 11", - "not op_mini all", - "not android < 76", - "not blackberry > 0", - "not op_mob > 0", - "not and_chr < 76", - "not and_ff < 68", - "not ie_mob > 0", - "not and_uc > 0", - "not samsung < 8.2", - "not and_qq > 0", - "not baidu > 0", - "not kaios > 0", -]; - -impl Engines { - pub fn from_browserslist(browserslist: Vec) -> Browsers { - browserslist::resolve(browserslist, &Default::default()) - .map(|b| b.into()) - .unwrap_or_default() - } - - // TODO Reinstate this so that engines.browsers are filtered out with ESMODULE_BROWSERS when - // we are using an esmodule output format - pub fn optimize(_engines: Engines, _output_format: OutputFormat) -> Engines { - todo!() - } - - pub fn supports(&self, feature: EnvironmentFeature) -> bool { - let min = feature.engines(); - macro_rules! check { - ($p: ident$(. $x: ident)*) => {{ - if let Some(v) = self.$p$(.$x)* { - match min.$p$(.$x)* { - None => return false, - Some(v2) if v < v2 => return false, - _ => {} - } - } - }}; - } - - check!(browsers.android); - check!(browsers.chrome); - check!(browsers.edge); - check!(browsers.firefox); - check!(browsers.ie); - check!(browsers.ios_saf); - check!(browsers.opera); - check!(browsers.safari); - check!(browsers.samsung); - true - } -} diff --git a/crates/parcel_core/src/types/environment/output_format.rs b/crates/parcel_core/src/types/environment/output_format.rs deleted file mode 100644 index d9c017889d6..00000000000 --- a/crates/parcel_core/src/types/environment/output_format.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::fmt::Display; - -use serde::{Deserialize, Serialize}; - -/// The JavaScript bundle output format -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum OutputFormat { - /// A CommonJS module that outputs require and module.exports - /// - /// This format is typically loaded in Node.js. - /// - CommonJS, - - /// An ES Module that outputs import and export statements - /// - /// ES Modules are often loaded using a \n`, - ); - }, - ); + let res = await run(b); + assert.equal( + res.default, + `

test

\n\n`, + ); + }); - it.v2( - "should inline a JS bundle's compiled text with `bundle-text` and HMR enabled", - async () => { - let b = await bundle( - path.join(__dirname, '/integration/bundle-text/javascript.js'), - { - hmrOptions: {}, - }, - ); + it("should inline a JS bundle's compiled text with `bundle-text` and HMR enabled", async () => { + let b = await bundle( + path.join(__dirname, '/integration/bundle-text/javascript.js'), + { + hmrOptions: {}, + }, + ); - let res = await run(b); - let log; - let ctx = vm.createContext({ - console: { - log(x) { - log = x; - }, + let res = await run(b); + let log; + let ctx = vm.createContext({ + console: { + log(x) { + log = x; }, - }); - vm.runInContext(res.default, ctx); - assert.equal(log, 'hi'); - }, - ); + }, + }); + vm.runInContext(res.default, ctx); + assert.equal(log, 'hi'); + }); - it.v2( - "should inline a JS bundle's compiled text with `bundle-text` with symbol propagation", - async () => { - let b = await bundle( - path.join(__dirname, '/integration/bundle-text/javascript.js'), - { - mode: 'production', - }, - ); + it("should inline a JS bundle's compiled text with `bundle-text` with symbol propagation", async () => { + let b = await bundle( + path.join(__dirname, '/integration/bundle-text/javascript.js'), + { + mode: 'production', + }, + ); - let res = await run(b); - let log; - let ctx = vm.createContext({ - console: { - log(x) { - log = x; - }, + let res = await run(b); + let log; + let ctx = vm.createContext({ + console: { + log(x) { + log = x; }, - }); - vm.runInContext(res, ctx); - assert.equal(log, 'hi'); - }, - ); + }, + }); + vm.runInContext(res, ctx); + assert.equal(log, 'hi'); + }); - it.v2( - "should inline a bundle's compiled text with `bundle-text` asynchronously", - async () => { - let b = await bundle( - path.join(__dirname, '/integration/bundle-text/async.js'), - ); + it("should inline a bundle's compiled text with `bundle-text` asynchronously", async () => { + let b = await bundle( + path.join(__dirname, '/integration/bundle-text/async.js'), + ); - let promise = (await run(b)).default; - assert.equal(typeof promise.then, 'function'); + let promise = (await run(b)).default; + assert.equal(typeof promise.then, 'function'); - let cssBundleContent = await promise; + let cssBundleContent = await promise; - assert( - cssBundleContent.startsWith( - `body { + assert( + cssBundleContent.startsWith( + `body { background-color: #000; } .svg-img { background-image: url("data:image/svg+xml,%3Csvg%3E%0A%0A%3C%2Fsvg%3E%0A"); }`, - ), - ); + ), + ); - assert(!cssBundleContent.includes('sourceMappingURL')); - }, - ); + assert(!cssBundleContent.includes('sourceMappingURL')); + }); - it.v2( - 'should inline text content as url-encoded text and mime type with `data-url:*` imports', - async () => { - let b = await bundle( - path.join(__dirname, '/integration/data-url/text.js'), - ); + it('should inline text content as url-encoded text and mime type with `data-url:*` imports', async () => { + let b = await bundle(path.join(__dirname, '/integration/data-url/text.js')); - assert.equal( - (await run(b)).default, - 'data:image/svg+xml,%3Csvg%20width%3D%22120%22%20height%3D%22120%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cfilter%20id%3D%22blur-_.%21~%2a%22%3E%0A%20%20%20%20%3CfeGaussianBlur%20stdDeviation%3D%225%22%3E%3C%2FfeGaussianBlur%3E%0A%20%20%3C%2Ffilter%3E%0A%20%20%3Ccircle%20cx%3D%2260%22%20cy%3D%2260%22%20r%3D%2250%22%20fill%3D%22green%22%20filter%3D%22url%28%27%23blur-_.%21~%2a%27%29%22%3E%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A', - ); - }, - ); + assert.equal( + (await run(b)).default, + 'data:image/svg+xml,%3Csvg%20width%3D%22120%22%20height%3D%22120%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cfilter%20id%3D%22blur-_.%21~%2a%22%3E%0A%20%20%20%20%3CfeGaussianBlur%20stdDeviation%3D%225%22%3E%3C%2FfeGaussianBlur%3E%0A%20%20%3C%2Ffilter%3E%0A%20%20%3Ccircle%20cx%3D%2260%22%20cy%3D%2260%22%20r%3D%2250%22%20fill%3D%22green%22%20filter%3D%22url%28%27%23blur-_.%21~%2a%27%29%22%3E%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A', + ); + }); - it.v2( - 'should inline binary content as url-encoded base64 and mime type with `data-url:*` imports', - async () => { - let b = await bundle( - path.join(__dirname, '/integration/data-url/binary.js'), - ); - ``; + it('should inline binary content as url-encoded base64 and mime type with `data-url:*` imports', async () => { + let b = await bundle( + path.join(__dirname, '/integration/data-url/binary.js'), + ); + ``; - assert((await run(b)).default.startsWith('data:image/webp;base64,UklGR')); - }, - ); + assert((await run(b)).default.startsWith('data:image/webp;base64,UklGR')); + }); - it.v2('should support both pipeline and non-pipeline imports', async () => { + it('should support both pipeline and non-pipeline imports', async () => { let b = await bundle( path.join(__dirname, '/integration/multi-pipeline/index.js'), ); @@ -3248,50 +3056,44 @@ describe('javascript', function () { assert.equal(await run(b), 2); }); - it.v2( - 'should detect requires in commonjs with plain template literals', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/commonjs-template-literal-plain/index.js', - ), - ); - let dist = await outputFS.readFile( - b.getBundles().find(b => b.type === 'js').filePath, - 'utf8', - ); - assert(dist.includes('$cPUKg$lodash = require("lodash");')); + it('should detect requires in commonjs with plain template literals', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/commonjs-template-literal-plain/index.js', + ), + ); + let dist = await outputFS.readFile( + b.getBundles().find(b => b.type === 'js').filePath, + 'utf8', + ); + assert(dist.includes('$cPUKg$lodash = require("lodash");')); - let add = await run(b); - assert.equal(add(2, 3), 5); - }, - ); + let add = await run(b); + assert.equal(add(2, 3), 5); + }); - it.v2( - `should detect requires in commonjs with plain template literals`, - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/commonjs-template-literal-interpolation/index.js', - ), - ); - let dist = await outputFS.readFile( - b.getBundles().find(b => b.type === 'js').filePath, - 'utf8', - ); + it(`should detect requires in commonjs with plain template literals`, async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/commonjs-template-literal-interpolation/index.js', + ), + ); + let dist = await outputFS.readFile( + b.getBundles().find(b => b.type === 'js').filePath, + 'utf8', + ); - assert( - dist.includes( - 'const add = require(`lodash/${$8cad8166811e0063$var$fn}`);', - ), - ); + assert( + dist.includes( + 'const add = require(`lodash/${$8cad8166811e0063$var$fn}`);', + ), + ); - let add = await run(b); - assert.equal(add(2, 3), 5); - }, - ); + let add = await run(b); + assert.equal(add(2, 3), 5); + }); it('only updates bundle names of changed bundles for browsers', async () => { let fixtureDir = path.join(__dirname, '/integration/name-invalidation'); @@ -3334,20 +3136,17 @@ describe('javascript', function () { ); }); - it.v2( - 'can load the same resource when referenced in multiple bundles', - async () => { - let b = await bundle( - path.join( - __dirname, - '/integration/same-resource-multiple-bundles/index.js', - ), - ); + it('can load the same resource when referenced in multiple bundles', async () => { + let b = await bundle( + path.join( + __dirname, + '/integration/same-resource-multiple-bundles/index.js', + ), + ); - let res = await run(b); - assert(url.parse(await res.default()).pathname.startsWith('/resource')); - }, - ); + let res = await run(b); + assert(url.parse(await res.default()).pathname.startsWith('/resource')); + }); it('can static import and dynamic import in the same bundle without creating a new bundle', async () => { let b = await bundle( @@ -3371,238 +3170,220 @@ describe('javascript', function () { assert.deepEqual(await (await run(b)).default, [42, 42, 42]); }); - it.v2( - 'async dependency can be resolved internally and externally from two different bundles', - async () => { - let b = await bundle( - ['entry1.js', 'entry2.js'].map(entry => - path.join( - __dirname, - '/integration/async-dep-internal-external/', - entry, - ), + it('async dependency can be resolved internally and externally from two different bundles', async () => { + let b = await bundle( + ['entry1.js', 'entry2.js'].map(entry => + path.join( + __dirname, + '/integration/async-dep-internal-external/', + entry, ), - { - mode: 'production', - defaultTargetOptions: { - shouldScopeHoist: true, - }, - }, - ); - - assertBundles(b, [ - { - assets: ['async.js'], - }, - { - name: 'entry1.js', - assets: ['child.js', 'entry1.js', 'async.js'], - }, - { - name: 'entry2.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'child.js', - 'entry2.js', - 'js-loader.js', - ], + ), + { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: true, }, - ]); - }, - ); - - it.v2( - 'can static import and dynamic import in the same bundle ancestry without creating a new bundle', - async () => { - let b = await bundle( - path.join(__dirname, '/integration/sync-async/same-ancestry.js'), - {mode: 'production', defaultTargetOptions: {shouldScopeHoist: false}}, - ); + }, + ); - assertBundles(b, [ - { - name: 'same-ancestry.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'dep.js', - 'js-loader.js', - 'same-ancestry.js', - 'esmodule-helpers.js', - ], - }, - { - assets: ['get-dep.js'], - }, - ]); + assertBundles(b, [ + { + assets: ['async.js'], + }, + { + name: 'entry1.js', + assets: ['child.js', 'entry1.js', 'async.js'], + }, + { + name: 'entry2.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'child.js', + 'entry2.js', + 'js-loader.js', + ], + }, + ]); + }); - assert.deepEqual(await (await run(b)).default, [42, 42]); - }, - ); + it('can static import and dynamic import in the same bundle ancestry without creating a new bundle', async () => { + let b = await bundle( + path.join(__dirname, '/integration/sync-async/same-ancestry.js'), + {mode: 'production', defaultTargetOptions: {shouldScopeHoist: false}}, + ); - it.v2( - 'can static import and dynamic import in the same bundle when another bundle requires async', - async () => { - let b = await bundle( - ['same-bundle.js', 'get-dep.js'].map(entry => - path.join(__dirname, '/integration/sync-async/', entry), - ), - { - mode: 'production', - defaultTargetOptions: { - shouldScopeHoist: false, - }, - }, - ); + assertBundles(b, [ + { + name: 'same-ancestry.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'dep.js', + 'js-loader.js', + 'same-ancestry.js', + 'esmodule-helpers.js', + ], + }, + { + assets: ['get-dep.js'], + }, + ]); - assertBundles(b, [ - { - assets: ['dep.js'], - }, - { - name: 'same-bundle.js', - assets: [ - 'same-bundle.js', - 'get-dep.js', - 'get-dep-2.js', - 'dep.js', - 'esmodule-helpers.js', - ], - }, - { - name: 'get-dep.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'get-dep.js', - 'js-loader.js', - 'esmodule-helpers.js', - ], + assert.deepEqual(await (await run(b)).default, [42, 42]); + }); + + it('can static import and dynamic import in the same bundle when another bundle requires async', async () => { + let b = await bundle( + ['same-bundle.js', 'get-dep.js'].map(entry => + path.join(__dirname, '/integration/sync-async/', entry), + ), + { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, }, - ]); + }, + ); - let bundles = b.getBundles(); - let sameBundle = bundles.find(b => b.name === 'same-bundle.js'); - let getDep = bundles.find(b => b.name === 'get-dep.js'); + assertBundles(b, [ + { + assets: ['dep.js'], + }, + { + name: 'same-bundle.js', + assets: [ + 'same-bundle.js', + 'get-dep.js', + 'get-dep-2.js', + 'dep.js', + 'esmodule-helpers.js', + ], + }, + { + name: 'get-dep.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'get-dep.js', + 'js-loader.js', + 'esmodule-helpers.js', + ], + }, + ]); - assert.deepEqual( - await ( - await runBundle(b, sameBundle) - ).default, - [42, 42, 42], - ); - assert.deepEqual(await (await runBundle(b, getDep)).default, 42); - }, - ); + let bundles = b.getBundles(); + let sameBundle = bundles.find(b => b.name === 'same-bundle.js'); + let getDep = bundles.find(b => b.name === 'get-dep.js'); - it.v2( - "can share dependencies between a shared bundle and its sibling's descendants", - async () => { - let b = await bundle( - path.join( - __dirname, - '/integration/shared-exports-for-sibling-descendant/index.js', - ), - { - mode: 'production', - defaultTargetOptions: { - shouldScopeHoist: false, - }, - }, - ); + assert.deepEqual( + await ( + await runBundle(b, sameBundle) + ).default, + [42, 42, 42], + ); + assert.deepEqual(await (await runBundle(b, getDep)).default, 42); + }); - assertBundles(b, [ - { - assets: ['wraps.js', 'lodash.js'], - }, - { - assets: ['a.js'], - }, - { - assets: ['child.js'], - }, - { - assets: ['grandchild.js'], - }, - { - assets: ['b.js'], - }, - { - name: 'index.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'index.js', - 'js-loader.js', - 'esmodule-helpers.js', - ], + it("can share dependencies between a shared bundle and its sibling's descendants", async () => { + let b = await bundle( + path.join( + __dirname, + '/integration/shared-exports-for-sibling-descendant/index.js', + ), + { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, }, - ]); + }, + ); - assert.deepEqual(await (await run(b)).default, [3, 5]); - }, - ); + assertBundles(b, [ + { + assets: ['wraps.js', 'lodash.js'], + }, + { + assets: ['a.js'], + }, + { + assets: ['child.js'], + }, + { + assets: ['grandchild.js'], + }, + { + assets: ['b.js'], + }, + { + name: 'index.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'index.js', + 'js-loader.js', + 'esmodule-helpers.js', + ], + }, + ]); - it.v2( - 'can run an entry bundle whose entry asset is present in another bundle', - async () => { - let b = await bundle( - ['index.js', 'value.js'].map(basename => - path.join(__dirname, '/integration/sync-entry-shared', basename), - ), - ); + assert.deepEqual(await (await run(b)).default, [3, 5]); + }); - assertBundles(b, [ - { - name: 'index.js', - assets: [ - 'index.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - 'esmodule-helpers.js', - ], - }, - {name: 'value.js', assets: ['value.js', 'esmodule-helpers.js']}, - {assets: ['async.js']}, - ]); + it('can run an entry bundle whose entry asset is present in another bundle', async () => { + let b = await bundle( + ['index.js', 'value.js'].map(basename => + path.join(__dirname, '/integration/sync-entry-shared', basename), + ), + ); - assert.equal(await (await run(b)).default, 43); - }, - ); + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + 'esmodule-helpers.js', + ], + }, + {name: 'value.js', assets: ['value.js', 'esmodule-helpers.js']}, + {assets: ['async.js']}, + ]); - it.v2( - 'can run an async bundle whose entry asset is present in another bundle', - async () => { - let b = await bundle( - path.join(__dirname, '/integration/async-entry-shared/index.js'), - ); + assert.equal(await (await run(b)).default, 43); + }); - assertBundles(b, [ - { - name: 'index.js', - assets: [ - 'index.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - 'esmodule-helpers.js', - ], - }, - {assets: ['value.js']}, - {assets: ['async.js']}, - ]); + it('can run an async bundle whose entry asset is present in another bundle', async () => { + let b = await bundle( + path.join(__dirname, '/integration/async-entry-shared/index.js'), + ); - assert.deepEqual(await (await run(b)).default, [42, 43]); - }, - ); + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + 'esmodule-helpers.js', + ], + }, + {assets: ['value.js']}, + {assets: ['async.js']}, + ]); - it.v2('should display a codeframe on a Terser parse error', async () => { + assert.deepEqual(await (await run(b)).default, [42, 43]); + }); + + it('should display a codeframe on a Terser parse error', async () => { let fixture = path.join(__dirname, 'integration/terser-codeframe/index.js'); let code = await inputFS.readFileSync(fixture, 'utf8'); await assert.rejects( @@ -3643,74 +3424,68 @@ describe('javascript', function () { ); }); - it.v2( - 'can run an async bundle that depends on a nonentry asset in a sibling', - async () => { - let b = await bundle( - ['index.js', 'other-entry.js'].map(basename => - path.join( - __dirname, - '/integration/async-entry-shared-sibling', - basename, - ), - ), - ); - - assertBundles(b, [ - { - name: 'index.js', - assets: [ - 'index.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - 'esmodule-helpers.js', - ], - }, - { - name: 'other-entry.js', - assets: [ - 'other-entry.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - ], - }, - {assets: ['a.js', 'value.js', 'esmodule-helpers.js']}, - {assets: ['b.js']}, - ]); - - assert.deepEqual(await (await run(b)).default, 43); - }, - ); - - it.v2( - 'can share sibling bundles reachable from a common dependency', - async () => { - let b = await bundle( + it('can run an async bundle that depends on a nonentry asset in a sibling', async () => { + let b = await bundle( + ['index.js', 'other-entry.js'].map(basename => path.join( __dirname, - '/integration/shared-sibling-common-dependency/index.js', + '/integration/async-entry-shared-sibling', + basename, ), - ); + ), + ); - let bundles = b.getBundles(); - let asyncJsBundles = bundles.filter( - b => !b.needsStableName && b.type === 'js', - ); - assert.equal(asyncJsBundles.length, 2); + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + 'esmodule-helpers.js', + ], + }, + { + name: 'other-entry.js', + assets: [ + 'other-entry.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + ], + }, + {assets: ['a.js', 'value.js', 'esmodule-helpers.js']}, + {assets: ['b.js']}, + ]); - // Every bundlegroup with an async js bundle should have the corresponding css - for (let bundle of asyncJsBundles) { - for (let bundleGroup of b.getBundleGroupsContainingBundle(bundle)) { - let bundlesInGroup = b.getBundlesInBundleGroup(bundleGroup); - assert(bundlesInGroup.find(s => s.type === 'css')); - } + assert.deepEqual(await (await run(b)).default, 43); + }); + + it('can share sibling bundles reachable from a common dependency', async () => { + let b = await bundle( + path.join( + __dirname, + '/integration/shared-sibling-common-dependency/index.js', + ), + ); + + let bundles = b.getBundles(); + let asyncJsBundles = bundles.filter( + b => !b.needsStableName && b.type === 'js', + ); + assert.equal(asyncJsBundles.length, 2); + + // Every bundlegroup with an async js bundle should have the corresponding css + for (let bundle of asyncJsBundles) { + for (let bundleGroup of b.getBundleGroupsContainingBundle(bundle)) { + let bundlesInGroup = b.getBundlesInBundleGroup(bundleGroup); + assert(bundlesInGroup.find(s => s.type === 'css')); } - }, - ); + } + }); - it.v2('should throw a diagnostic for unknown pipelines', async function () { + it('should throw a diagnostic for unknown pipelines', async function () { let fixture = path.join(__dirname, 'integration/pipeline-unknown/a.js'); let code = await inputFS.readFileSync(fixture, 'utf8'); await assert.rejects(() => bundle(fixture), { @@ -3747,7 +3522,7 @@ describe('javascript', function () { }); }); - it.v2('can create a bundle starting with a dot', async function () { + it('can create a bundle starting with a dot', async function () { let b = await bundle( path.join(__dirname, '/integration/dotfile-bundle/index.js'), ); @@ -3760,23 +3535,20 @@ describe('javascript', function () { ]); }); - it.v2( - 'should not automatically name bundle files starting with a dot', - async function () { - await bundle( - path.join(__dirname, '/integration/bundle-naming/.invisible/index.js'), - ); - let bundleFiles = await outputFS.readdir(distDir); - let renamedSomeFiles = bundleFiles.some(currFile => - currFile.startsWith('invisible.'), - ); - let namedWithDot = bundleFiles.some(currFile => - currFile.startsWith('.invisible.'), - ); - assert.equal(renamedSomeFiles, true); - assert.equal(namedWithDot, false); - }, - ); + it('should not automatically name bundle files starting with a dot', async function () { + await bundle( + path.join(__dirname, '/integration/bundle-naming/.invisible/index.js'), + ); + let bundleFiles = await outputFS.readdir(distDir); + let renamedSomeFiles = bundleFiles.some(currFile => + currFile.startsWith('invisible.'), + ); + let namedWithDot = bundleFiles.some(currFile => + currFile.startsWith('.invisible.'), + ); + assert.equal(renamedSomeFiles, true); + assert.equal(namedWithDot, false); + }); it('should support duplicate re-exports without scope hoisting', async function () { let b = await bundle( @@ -3820,22 +3592,19 @@ describe('javascript', function () { assert.deepEqual(res, {a: 4}); }); - it.v2( - 'should not use arrow functions for reexport declarations unless supported', - async function () { - let b = await bundle( - path.join(__dirname, 'integration/js-export-arrow-support/index.js'), - { - // Remove comments containing "=>" - defaultTargetOptions: { - shouldOptimize: true, - }, + it('should not use arrow functions for reexport declarations unless supported', async function () { + let b = await bundle( + path.join(__dirname, 'integration/js-export-arrow-support/index.js'), + { + // Remove comments containing "=>" + defaultTargetOptions: { + shouldOptimize: true, }, - ); - let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); - assert(!content.includes('=>')); - }, - ); + }, + ); + let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); + assert(!content.includes('=>')); + }); it('should support import namespace declarations of other ES modules', async function () { let b = await bundle( @@ -4106,386 +3875,365 @@ describe('javascript', function () { assert.equal(await res, true); }); - it.v2( - 'should support runtime module deduplication with scope hoisting', - async function () { - let b = await bundle( - path.join(__dirname, 'integration/js-runtime-dedup/index.js'), - { - mode: 'production', - }, - ); + it('should support runtime module deduplication with scope hoisting', async function () { + let b = await bundle( + path.join(__dirname, 'integration/js-runtime-dedup/index.js'), + { + mode: 'production', + }, + ); - assertBundles(b, [ - { - name: 'index.js', - assets: [ - 'index.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - 'bundle-manifest.js', - ], - }, - { - assets: ['async1.js', 'shared.js'], - }, - { - assets: ['async2.js', 'shared.js'], - }, - ]); + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + 'bundle-manifest.js', + ], + }, + { + assets: ['async1.js', 'shared.js'], + }, + { + assets: ['async2.js', 'shared.js'], + }, + ]); - let res = await run(b); - assert.equal(await res, true); - }, - ); + let res = await run(b); + assert.equal(await res, true); + }); - it.v2( - 'should remap locations in diagnostics using the input source map', - async () => { - let fixture = path.join( - __dirname, - 'integration/diagnostic-sourcemap/index.js', - ); - let code = await inputFS.readFileSync(fixture, 'utf8'); - await assert.rejects( - () => - bundle(fixture, { - defaultTargetOptions: { - shouldOptimize: true, - }, - }), - { - name: 'BuildError', - diagnostics: [ - { - message: "Failed to resolve 'foo' from './index.js'", - origin: '@parcel/core', - codeFrames: [ - { - filePath: fixture, - code, - codeHighlights: [ - { - message: undefined, - start: { - line: 11, - column: 17, - }, - end: { - line: 11, - column: 21, - }, + it('should remap locations in diagnostics using the input source map', async () => { + let fixture = path.join( + __dirname, + 'integration/diagnostic-sourcemap/index.js', + ); + let code = await inputFS.readFileSync(fixture, 'utf8'); + await assert.rejects( + () => + bundle(fixture, { + defaultTargetOptions: { + shouldOptimize: true, + }, + }), + { + name: 'BuildError', + diagnostics: [ + { + message: "Failed to resolve 'foo' from './index.js'", + origin: '@parcel/core', + codeFrames: [ + { + filePath: fixture, + code, + codeHighlights: [ + { + message: undefined, + start: { + line: 11, + column: 17, }, - ], - }, - ], - }, - { - message: "Cannot find module 'foo'", - origin: '@parcel/resolver-default', - hints: [], - }, - ], + end: { + line: 11, + column: 21, + }, + }, + ], + }, + ], + }, + { + message: "Cannot find module 'foo'", + origin: '@parcel/resolver-default', + hints: [], + }, + ], + }, + ); + }); + + it('should reuse a bundle when its main asset (aka bundleroot) is imported sychronously', async function () { + let b = await bundle( + path.join(__dirname, 'integration/shared-bundle-single-source/index.js'), + { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, }, - ); - }, - ); + }, + ); - it.v2( - 'should reuse a bundle when its main asset (aka bundleroot) is imported sychronously', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/shared-bundle-single-source/index.js', - ), - { + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'css-loader.js', + 'esmodule-helpers.js', + 'js-loader.js', + 'bundle-manifest.js', + ], + }, + { + assets: ['bar.js'], + }, + { + assets: ['a.js', 'b.js', 'foo.js'], + }, + { + assets: ['styles.css'], + }, + { + assets: ['local.html'], + }, + ]); + }); + + it('should error on undeclared external dependencies for libraries', async function () { + let fixture = path.join( + __dirname, + 'integration/undeclared-external/index.js', + ); + let pkg = path.join( + __dirname, + 'integration/undeclared-external/package.json', + ); + await assert.rejects( + () => + bundle(fixture, { mode: 'production', defaultTargetOptions: { - shouldScopeHoist: false, + shouldOptimize: false, }, - }, - ); - - assertBundles(b, [ - { - name: 'index.js', - assets: [ - 'index.js', - 'bundle-url.js', - 'cacheLoader.js', - 'css-loader.js', - 'esmodule-helpers.js', - 'js-loader.js', - 'bundle-manifest.js', - ], - }, - { - assets: ['bar.js'], - }, - { - assets: ['a.js', 'b.js', 'foo.js'], - }, - { - assets: ['styles.css'], - }, - { - assets: ['local.html'], - }, - ]); - }, - ); - - it.v2( - 'should error on undeclared external dependencies for libraries', - async function () { - let fixture = path.join( - __dirname, - 'integration/undeclared-external/index.js', - ); - let pkg = path.join( - __dirname, - 'integration/undeclared-external/package.json', - ); - await assert.rejects( - () => - bundle(fixture, { - mode: 'production', - defaultTargetOptions: { - shouldOptimize: false, - }, - }), - { - name: 'BuildError', - diagnostics: [ - { - message: "Failed to resolve 'lodash' from './index.js'", - origin: '@parcel/core', - codeFrames: [ - { - code: await inputFS.readFile(fixture, 'utf8'), - filePath: fixture, - codeHighlights: [ - { - message: undefined, - start: { - line: 1, - column: 19, - }, - end: { - line: 1, - column: 26, - }, + }), + { + name: 'BuildError', + diagnostics: [ + { + message: "Failed to resolve 'lodash' from './index.js'", + origin: '@parcel/core', + codeFrames: [ + { + code: await inputFS.readFile(fixture, 'utf8'), + filePath: fixture, + codeHighlights: [ + { + message: undefined, + start: { + line: 1, + column: 19, }, - ], - }, - ], - }, - { - message: - 'External dependency "lodash" is not declared in package.json.', - origin: '@parcel/resolver-default', - codeFrames: [ - { - code: await inputFS.readFile(pkg, 'utf8'), - filePath: pkg, - language: 'json', - codeHighlights: [ - { - message: undefined, - start: { - line: 5, - column: 3, - }, - end: { - line: 5, - column: 16, - }, + end: { + line: 1, + column: 26, }, - ], - }, - ], - hints: ['Add "lodash" as a dependency.'], - }, - ], - }, - ); - }, - ); - - it.v2( - 'should error on undeclared helpers dependency for libraries', - async function () { - let fixture = path.join( - __dirname, - 'integration/undeclared-external/helpers.js', - ); - let pkg = path.join( - __dirname, - 'integration/undeclared-external/package.json', - ); - await assert.rejects( - () => - bundle(fixture, { - mode: 'production', - defaultTargetOptions: { - shouldOptimize: false, - }, - }), - { - name: 'BuildError', - diagnostics: [ - { - message: md`Failed to resolve '${'@swc/helpers/cjs/_class_call_check.cjs'}' from '${normalizePath( - require.resolve('@parcel/transformer-js/src/JSTransformer.js'), - )}'`, - origin: '@parcel/core', - codeFrames: [ - { - code: await inputFS.readFile(fixture, 'utf8'), - filePath: fixture, - codeHighlights: [ - { - message: undefined, - start: { - line: 1, - column: 1, - }, - end: { - line: 1, - column: 1, - }, + }, + ], + }, + ], + }, + { + message: + 'External dependency "lodash" is not declared in package.json.', + origin: '@parcel/resolver-default', + codeFrames: [ + { + code: await inputFS.readFile(pkg, 'utf8'), + filePath: pkg, + language: 'json', + codeHighlights: [ + { + message: undefined, + start: { + line: 5, + column: 3, }, - ], - }, - ], - }, - { - message: - 'External dependency "@swc/helpers" is not declared in package.json.', - origin: '@parcel/resolver-default', - codeFrames: [ - { - code: await inputFS.readFile(pkg, 'utf8'), - filePath: pkg, - language: 'json', - codeHighlights: [ - { - message: undefined, - start: { - line: 5, - column: 3, - }, - end: { - line: 5, - column: 16, - }, + end: { + line: 5, + column: 16, }, - ], - }, - ], - hints: ['Add "@swc/helpers" as a dependency.'], - }, - ], - }, - ); - }, - ); + }, + ], + }, + ], + hints: ['Add "lodash" as a dependency.'], + }, + ], + }, + ); + }); - it.v2( - 'should error on mismatched helpers version for libraries', - async function () { - let fixture = path.join( - __dirname, - 'integration/undeclared-external/helpers.js', - ); - let pkg = path.join( - __dirname, - 'integration/undeclared-external/package.json', - ); - let pkgContents = JSON.stringify( - { - ...JSON.parse(await overlayFS.readFile(pkg, 'utf8')), - dependencies: { - '@swc/helpers': '^0.3.0', + it('should error on undeclared helpers dependency for libraries', async function () { + let fixture = path.join( + __dirname, + 'integration/undeclared-external/helpers.js', + ); + let pkg = path.join( + __dirname, + 'integration/undeclared-external/package.json', + ); + await assert.rejects( + () => + bundle(fixture, { + mode: 'production', + defaultTargetOptions: { + shouldOptimize: false, }, - }, - false, - 2, - ); - await overlayFS.mkdirp(path.dirname(pkg)); - await overlayFS.writeFile(pkg, pkgContents); - await assert.rejects( - () => - bundle(fixture, { - mode: 'production', - inputFS: overlayFS, - defaultTargetOptions: { - shouldOptimize: false, - }, - }), - { - name: 'BuildError', - diagnostics: [ - { - message: md`Failed to resolve '${'@swc/helpers/cjs/_class_call_check.cjs'}' from '${normalizePath( - require.resolve('@parcel/transformer-js/src/JSTransformer.js'), - )}'`, - origin: '@parcel/core', - codeFrames: [ - { - code: await inputFS.readFile(fixture, 'utf8'), - filePath: fixture, - codeHighlights: [ - { - message: undefined, - start: { - line: 1, - column: 1, - }, - end: { - line: 1, - column: 1, - }, + }), + { + name: 'BuildError', + diagnostics: [ + { + message: md`Failed to resolve '${'@swc/helpers/cjs/_class_call_check.cjs'}' from '${normalizePath( + require.resolve('@parcel/transformer-js/src/JSTransformer.js'), + )}'`, + origin: '@parcel/core', + codeFrames: [ + { + code: await inputFS.readFile(fixture, 'utf8'), + filePath: fixture, + codeHighlights: [ + { + message: undefined, + start: { + line: 1, + column: 1, }, - ], - }, - ], - }, - { - message: - 'External dependency "@swc/helpers" does not satisfy required semver range "^0.5.0".', - origin: '@parcel/resolver-default', - codeFrames: [ - { - code: pkgContents, - filePath: pkg, - language: 'json', - codeHighlights: [ - { - message: 'Found this conflicting requirement.', - start: { - line: 6, - column: 21, - }, - end: { - line: 6, - column: 28, - }, + end: { + line: 1, + column: 1, }, - ], - }, - ], - hints: [ - 'Update the dependency on "@swc/helpers" to satisfy "^0.5.0".', - ], - }, - ], + }, + ], + }, + ], + }, + { + message: + 'External dependency "@swc/helpers" is not declared in package.json.', + origin: '@parcel/resolver-default', + codeFrames: [ + { + code: await inputFS.readFile(pkg, 'utf8'), + filePath: pkg, + language: 'json', + codeHighlights: [ + { + message: undefined, + start: { + line: 5, + column: 3, + }, + end: { + line: 5, + column: 16, + }, + }, + ], + }, + ], + hints: ['Add "@swc/helpers" as a dependency.'], + }, + ], + }, + ); + }); + + it('should error on mismatched helpers version for libraries', async function () { + let fixture = path.join( + __dirname, + 'integration/undeclared-external/helpers.js', + ); + let pkg = path.join( + __dirname, + 'integration/undeclared-external/package.json', + ); + let pkgContents = JSON.stringify( + { + ...JSON.parse(await overlayFS.readFile(pkg, 'utf8')), + dependencies: { + '@swc/helpers': '^0.3.0', }, - ); - }, - ); + }, + false, + 2, + ); + await overlayFS.mkdirp(path.dirname(pkg)); + await overlayFS.writeFile(pkg, pkgContents); + await assert.rejects( + () => + bundle(fixture, { + mode: 'production', + inputFS: overlayFS, + defaultTargetOptions: { + shouldOptimize: false, + }, + }), + { + name: 'BuildError', + diagnostics: [ + { + message: md`Failed to resolve '${'@swc/helpers/cjs/_class_call_check.cjs'}' from '${normalizePath( + require.resolve('@parcel/transformer-js/src/JSTransformer.js'), + )}'`, + origin: '@parcel/core', + codeFrames: [ + { + code: await inputFS.readFile(fixture, 'utf8'), + filePath: fixture, + codeHighlights: [ + { + message: undefined, + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 1, + }, + }, + ], + }, + ], + }, + { + message: + 'External dependency "@swc/helpers" does not satisfy required semver range "^0.5.0".', + origin: '@parcel/resolver-default', + codeFrames: [ + { + code: pkgContents, + filePath: pkg, + language: 'json', + codeHighlights: [ + { + message: 'Found this conflicting requirement.', + start: { + line: 6, + column: 21, + }, + end: { + line: 6, + column: 28, + }, + }, + ], + }, + ], + hints: [ + 'Update the dependency on "@swc/helpers" to satisfy "^0.5.0".', + ], + }, + ], + }, + ); + }); describe('multiple import types', function () { it('supports both static and dynamic imports to the same specifier in the same file', async function () { @@ -4509,36 +4257,33 @@ describe('javascript', function () { assert.equal(res.Foo, await res.LazyFoo); }); - it.v2( - 'supports both static and dynamic imports to the same specifier in the same file with scope hoisting', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/static-dynamic.js', - ), - { - defaultTargetOptions: { - outputFormat: 'esmodule', - isLibrary: true, - shouldScopeHoist: true, - }, + it('supports both static and dynamic imports to the same specifier in the same file with scope hoisting', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/static-dynamic.js', + ), + { + defaultTargetOptions: { + outputFormat: 'esmodule', + isLibrary: true, + shouldScopeHoist: true, }, - ); + }, + ); - assertBundles(b, [ - { - type: 'js', - assets: ['static-dynamic.js', 'other.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: ['static-dynamic.js', 'other.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.Foo, 'function'); - assert.equal(typeof res.LazyFoo, 'object'); - assert.equal(res.Foo, await res.LazyFoo); - }, - ); + let res = await run(b); + assert.equal(typeof res.Foo, 'function'); + assert.equal(typeof res.LazyFoo, 'object'); + assert.equal(res.Foo, await res.LazyFoo); + }); it('supports static, dynamic, and url to the same specifier in the same file', async function () { let b = await bundle( @@ -4576,63 +4321,60 @@ describe('javascript', function () { ); }); - it.v2( - 'supports static, dynamic, and url to the same specifier in the same file with scope hoisting', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/static-dynamic-url.js', - ), - { - defaultTargetOptions: { - outputFormat: 'esmodule', - isLibrary: true, - shouldScopeHoist: true, - }, - }, - ); - - assertBundles(b, [ - { - type: 'js', - assets: ['static-dynamic-url.js', 'other.js'], - }, - { - type: 'js', - assets: ['other.js'], - }, - ]); - - let res = await run(b); - assert.equal(typeof res.Foo, 'function'); - assert.equal(typeof res.LazyFoo, 'object'); - assert.equal(res.Foo, await res.LazyFoo); - assert.equal( - res.url, - 'http://localhost/' + path.basename(b.getBundles()[1].filePath), - ); - }, - ); - - it('supports dynamic import and url to the same specifier in the same file', async function () { + it('supports static, dynamic, and url to the same specifier in the same file with scope hoisting', async function () { let b = await bundle( path.join( __dirname, - 'integration/multiple-import-types/dynamic-url.js', + 'integration/multiple-import-types/static-dynamic-url.js', ), + { + defaultTargetOptions: { + outputFormat: 'esmodule', + isLibrary: true, + shouldScopeHoist: true, + }, + }, ); assertBundles(b, [ { type: 'js', - assets: [ - 'dynamic-url.js', - 'esmodule-helpers.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - ], + assets: ['static-dynamic-url.js', 'other.js'], + }, + { + type: 'js', + assets: ['other.js'], + }, + ]); + + let res = await run(b); + assert.equal(typeof res.Foo, 'function'); + assert.equal(typeof res.LazyFoo, 'object'); + assert.equal(res.Foo, await res.LazyFoo); + assert.equal( + res.url, + 'http://localhost/' + path.basename(b.getBundles()[1].filePath), + ); + }); + + it('supports dynamic import and url to the same specifier in the same file', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/dynamic-url.js', + ), + ); + + assertBundles(b, [ + { + type: 'js', + assets: [ + 'dynamic-url.js', + 'esmodule-helpers.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + ], }, { type: 'js', @@ -4648,181 +4390,166 @@ describe('javascript', function () { ); }); - it.v2( - 'supports dynamic import and url to the same specifier in the same file with scope hoisting', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/dynamic-url.js', - ), - { - defaultTargetOptions: { - outputFormat: 'esmodule', - isLibrary: true, - shouldScopeHoist: true, - }, + it('supports dynamic import and url to the same specifier in the same file with scope hoisting', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/dynamic-url.js', + ), + { + defaultTargetOptions: { + outputFormat: 'esmodule', + isLibrary: true, + shouldScopeHoist: true, }, - ); + }, + ); - assertBundles(b, [ - { - type: 'js', - assets: ['dynamic-url.js'], - }, - { - type: 'js', - assets: ['other.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: ['dynamic-url.js'], + }, + { + type: 'js', + assets: ['other.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.lazy, 'object'); - assert.equal(typeof (await res.lazy), 'function'); - assert.equal( - res.url, - 'http://localhost/' + path.basename(b.getBundles()[1].filePath), - ); - }, - ); + let res = await run(b); + assert.equal(typeof res.lazy, 'object'); + assert.equal(typeof (await res.lazy), 'function'); + assert.equal( + res.url, + 'http://localhost/' + path.basename(b.getBundles()[1].filePath), + ); + }); - it.v2( - 'supports static import and inline bundle for the same asset', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/static-inline.js', - ), - ); + it('supports static import and inline bundle for the same asset', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/static-inline.js', + ), + ); - assertBundles(b, [ - { - type: 'js', - assets: ['static-inline.js', 'other.js', 'esmodule-helpers.js'], - }, - { - type: 'js', - assets: ['other.js', 'esmodule-helpers.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: ['static-inline.js', 'other.js', 'esmodule-helpers.js'], + }, + { + type: 'js', + assets: ['other.js', 'esmodule-helpers.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.Foo, 'function'); - assert.equal(typeof res.text, 'string'); - }, - ); + let res = await run(b); + assert.equal(typeof res.Foo, 'function'); + assert.equal(typeof res.text, 'string'); + }); - it.v2( - 'supports static import and inline bundle for the same asset with scope hoisting', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/static-inline.js', - ), - { - defaultTargetOptions: { - outputFormat: 'esmodule', - isLibrary: true, - shouldScopeHoist: true, - }, + it('supports static import and inline bundle for the same asset with scope hoisting', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/static-inline.js', + ), + { + defaultTargetOptions: { + outputFormat: 'esmodule', + isLibrary: true, + shouldScopeHoist: true, }, - ); + }, + ); - assertBundles(b, [ - { - type: 'js', - assets: ['static-inline.js', 'other.js'], - }, - { - type: 'js', - assets: ['other.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: ['static-inline.js', 'other.js'], + }, + { + type: 'js', + assets: ['other.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.Foo, 'function'); - assert.equal(typeof res.text, 'string'); - }, - ); + let res = await run(b); + assert.equal(typeof res.Foo, 'function'); + assert.equal(typeof res.text, 'string'); + }); - it.v2( - 'supports dynamic import and inline bundle for the same asset', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/dynamic-inline.js', - ), - ); + it('supports dynamic import and inline bundle for the same asset', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/dynamic-inline.js', + ), + ); - assertBundles(b, [ - { - type: 'js', - assets: [ - 'dynamic-inline.js', - 'esmodule-helpers.js', - 'bundle-url.js', - 'cacheLoader.js', - 'js-loader.js', - ], - }, - { - type: 'js', - assets: ['other.js'], - }, - { - type: 'js', - assets: ['other.js', 'esmodule-helpers.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: [ + 'dynamic-inline.js', + 'esmodule-helpers.js', + 'bundle-url.js', + 'cacheLoader.js', + 'js-loader.js', + ], + }, + { + type: 'js', + assets: ['other.js'], + }, + { + type: 'js', + assets: ['other.js', 'esmodule-helpers.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.lazy, 'object'); - assert.equal(typeof (await res.lazy), 'function'); - assert.equal(typeof res.text, 'string'); - }, - ); + let res = await run(b); + assert.equal(typeof res.lazy, 'object'); + assert.equal(typeof (await res.lazy), 'function'); + assert.equal(typeof res.text, 'string'); + }); - it.v2( - 'supports dynamic import and inline bundle for the same asset with scope hoisting', - async function () { - let b = await bundle( - path.join( - __dirname, - 'integration/multiple-import-types/dynamic-inline.js', - ), - { - defaultTargetOptions: { - outputFormat: 'esmodule', - isLibrary: true, - shouldScopeHoist: true, - }, + it('supports dynamic import and inline bundle for the same asset with scope hoisting', async function () { + let b = await bundle( + path.join( + __dirname, + 'integration/multiple-import-types/dynamic-inline.js', + ), + { + defaultTargetOptions: { + outputFormat: 'esmodule', + isLibrary: true, + shouldScopeHoist: true, }, - ); + }, + ); - assertBundles(b, [ - { - type: 'js', - assets: ['dynamic-inline.js'], - }, - { - type: 'js', - assets: ['other.js'], - }, - { - type: 'js', - assets: ['other.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: ['dynamic-inline.js'], + }, + { + type: 'js', + assets: ['other.js'], + }, + { + type: 'js', + assets: ['other.js'], + }, + ]); - let res = await run(b); - assert.equal(typeof res.lazy, 'object'); - assert.equal(typeof (await res.lazy), 'function'); - assert.equal(typeof res.text, 'string'); - }, - ); + let res = await run(b); + assert.equal(typeof res.lazy, 'object'); + assert.equal(typeof (await res.lazy), 'function'); + assert.equal(typeof res.text, 'string'); + }); }); it('should avoid creating a bundle for lazy dependencies already available in a shared bundle', async function () { @@ -4865,85 +4592,79 @@ describe('javascript', function () { }); }); - it.v2( - 'should support importing async bundles from bundles with different dist paths', - async function () { - let bundleGraph = await bundle( - ['bar/entry/entry-a.js', 'foo/entry-b.js'].map(f => - path.join(__dirname, 'integration/differing-bundle-urls', f), - ), - { - mode: 'production', - defaultTargetOptions: { - shouldOptimize: false, - }, + it('should support importing async bundles from bundles with different dist paths', async function () { + let bundleGraph = await bundle( + ['bar/entry/entry-a.js', 'foo/entry-b.js'].map(f => + path.join(__dirname, 'integration/differing-bundle-urls', f), + ), + { + mode: 'production', + defaultTargetOptions: { + shouldOptimize: false, }, - ); - assertBundles(bundleGraph, [ - { - name: 'entry-a.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'entry-a.js', - 'js-loader.js', - ], - }, - { - name: 'entry-b.js', - assets: [ - 'bundle-manifest.js', - 'bundle-url.js', - 'cacheLoader.js', - 'entry-b.js', - 'js-loader.js', - ], - }, - {name: /deep\.[a-f0-9]+\.js/, assets: ['deep.js']}, - {name: /common\.[a-f0-9]+\.js/, assets: ['index.js']}, - ]); + }, + ); + assertBundles(bundleGraph, [ + { + name: 'entry-a.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'entry-a.js', + 'js-loader.js', + ], + }, + { + name: 'entry-b.js', + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'entry-b.js', + 'js-loader.js', + ], + }, + {name: /deep\.[a-f0-9]+\.js/, assets: ['deep.js']}, + {name: /common\.[a-f0-9]+\.js/, assets: ['index.js']}, + ]); - let [a, b] = bundleGraph.getBundles().filter(b => b.needsStableName); - let calls = []; + let [a, b] = bundleGraph.getBundles().filter(b => b.needsStableName); + let calls = []; - let bundles = [ - [await outputFS.readFile(a.filePath, 'utf8'), a], - [await outputFS.readFile(b.filePath, 'utf8'), b], - ]; + let bundles = [ + [await outputFS.readFile(a.filePath, 'utf8'), a], + [await outputFS.readFile(b.filePath, 'utf8'), b], + ]; - await runBundles(bundleGraph, a, bundles, { - sideEffect: v => { - calls.push(v); - }, - }); + await runBundles(bundleGraph, a, bundles, { + sideEffect: v => { + calls.push(v); + }, + }); - assert.deepEqual(calls, ['common', 'deep']); - }, - ); + assert.deepEqual(calls, ['common', 'deep']); + }); - it.v2( - 'supports deferring unused ESM imports with sideEffects: false', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/side-effects-false/import.js'), - ); + it('supports deferring unused ESM imports with sideEffects: false', async function () { + let b = await bundle( + path.join(__dirname, '/integration/side-effects-false/import.js'), + ); - let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); + let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); - assert(!content.includes('returned from bar')); + assert(!content.includes('returned from bar')); - let called = false; - let output = await run(b, { - sideEffect() { - called = true; - }, - }); + let called = false; + let output = await run(b, { + sideEffect() { + called = true; + }, + }); - assert(!called, 'side effect called'); - assert.strictEqual(output.default, 4); - }, - ); + assert(!called, 'side effect called'); + assert.strictEqual(output.default, 4); + }); it('supports ESM imports and requires with sideEffects: false', async function () { let b = await bundle( @@ -4984,32 +4705,29 @@ describe('javascript', function () { ); }); - it.v2( - 'should produce working output with both scope hoisting and non scope hoisting targets', - async function () { - let b = await bundle( - path.join(__dirname, '/integration/re-export-no-scope-hoist'), - { - defaultTargetOptions: { - shouldScopeHoist: true, - }, + it('should produce working output with both scope hoisting and non scope hoisting targets', async function () { + let b = await bundle( + path.join(__dirname, '/integration/re-export-no-scope-hoist'), + { + defaultTargetOptions: { + shouldScopeHoist: true, }, - ); - let bundles = b.getBundles(); + }, + ); + let bundles = b.getBundles(); - let o1, o2; - await runBundle(b, bundles[0], { - output: (...o) => (o1 = o), - }); + let o1, o2; + await runBundle(b, bundles[0], { + output: (...o) => (o1 = o), + }); - await runBundle(b, bundles[1], { - output: (...o) => (o2 = o), - }); + await runBundle(b, bundles[1], { + output: (...o) => (o2 = o), + }); - assert.deepEqual(o1, ['UIIcon', 'Icon']); - assert.deepEqual(o2, ['UIIcon', 'Icon']); - }, - ); + assert.deepEqual(o1, ['UIIcon', 'Icon']); + assert.deepEqual(o2, ['UIIcon', 'Icon']); + }); it('should not deduplicate an asset if it will become unreachable', async function () { let b = await bundle( @@ -5087,13 +4805,11 @@ describe('javascript', function () { assert(!contents.includes('\ufffe')); }); - it.v2( - `should not wrap assets that are duplicated in different targets`, - async function () { - const dir = path.join(__dirname, 'multi-target-duplicates'); - overlayFS.mkdirp(dir); + it(`should not wrap assets that are duplicated in different targets`, async function () { + const dir = path.join(__dirname, 'multi-target-duplicates'); + overlayFS.mkdirp(dir); - await fsFixture(overlayFS, dir)` + await fsFixture(overlayFS, dir)` shared/index.js: export default 2; @@ -5118,27 +4834,24 @@ describe('javascript', function () { export default shared + 2; `; - let b = await bundle(path.join(dir, '/packages/*'), { - inputFS: overlayFS, - }); + let b = await bundle(path.join(dir, '/packages/*'), { + inputFS: overlayFS, + }); - for (let bundle of b.getBundles()) { - let contents = await outputFS.readFile(bundle.filePath, 'utf8'); - assert( - !contents.includes('parcelRequire'), - 'should not include parcelRequire', - ); - } - }, - ); + for (let bundle of b.getBundles()) { + let contents = await outputFS.readFile(bundle.filePath, 'utf8'); + assert( + !contents.includes('parcelRequire'), + 'should not include parcelRequire', + ); + } + }); - it.v2( - `should not wrap assets that are duplicated in different targets in the same dist dir`, - async function () { - const dir = path.join(__dirname, 'multi-target-duplicates-dist'); - overlayFS.mkdirp(dir); + it(`should not wrap assets that are duplicated in different targets in the same dist dir`, async function () { + const dir = path.join(__dirname, 'multi-target-duplicates-dist'); + overlayFS.mkdirp(dir); - await fsFixture(overlayFS, dir)` + await fsFixture(overlayFS, dir)` package.json: { "main": "dist/main.cjs", @@ -5172,21 +4885,20 @@ describe('javascript', function () { export default 2; `; - let b = await bundle(dir, { - inputFS: overlayFS, - }); + let b = await bundle(dir, { + inputFS: overlayFS, + }); - for (let bundle of b.getBundles()) { - let contents = await outputFS.readFile(bundle.filePath, 'utf8'); - assert( - !contents.includes('parcelRequire'), - 'should not include parcelRequire', - ); - } - }, - ); + for (let bundle of b.getBundles()) { + let contents = await outputFS.readFile(bundle.filePath, 'utf8'); + assert( + !contents.includes('parcelRequire'), + 'should not include parcelRequire', + ); + } + }); - it.v2(`should also fail on recoverable parse errors`, async () => { + it(`should also fail on recoverable parse errors`, async () => { await fsFixture(overlayFS, __dirname)` js-recoverable-parse-errors index.js: @@ -5245,7 +4957,7 @@ describe('javascript', function () { shouldScopeHoist ? '' : 'out' } scope-hoisting`, function () { if (usesSymbolPropagation) { - it.v2('supports excluding unused CSS imports', async function () { + it('supports excluding unused CSS imports', async function () { let b = await bundle( path.join( __dirname, @@ -5290,230 +5002,111 @@ describe('javascript', function () { assert(!css.includes('.b2')); }); - it.v2( - "doesn't create new bundles for dynamic imports in excluded assets", - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-no-new-bundle/index.html', - ), - options, - ); - - assertBundles(b, [ - { - name: 'index.html', - assets: ['index.html'], - }, - { - type: 'js', - assets: ['index.js', 'b1.js'], - }, - ]); - - let calls = []; - let res = await run( - b, - { - output: null, - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, - ); - assert.deepEqual(calls, ['b1']); - assert.deepEqual(res.output, 2); - }, - ); - } - - it.v2( - 'supports deferring unused ES6 re-exports (namespace used)', - async function () { + it("doesn't create new bundles for dynamic imports in excluded assets", async function () { let b = await bundle( path.join( __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/a.js', + '/integration/scope-hoisting/es6/side-effects-no-new-bundle/index.html', ), options, ); assertBundles(b, [ { - type: 'js', - assets: usesSymbolPropagation - ? ['a.js', 'message1.js'] - : ['a.js', 'esmodule-helpers.js', 'index.js', 'message1.js'], + name: 'index.html', + assets: ['index.html'], }, - ]); - - if (usesSymbolPropagation) { - // TODO this only excluded, but should be deferred. - assert(!findAsset(b, 'message3.js')); - } - - let calls = []; - let res = await run( - b, { - sideEffect: caller => { - calls.push(caller); - }, + type: 'js', + assets: ['index.js', 'b1.js'], }, - {require: false}, - ); - - assert.deepEqual( - calls, - shouldScopeHoist ? ['message1'] : ['message1', 'index'], - ); - assert.deepEqual(res.output, 'Message 1'); - }, - ); - - it.v2( - 'supports deferring an unused ES6 re-export (wildcard, empty, unused)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-all-empty/a.js', - ), - options, - ); - - if (usesSymbolPropagation) { - assertDependencyWasExcluded(b, 'index.js', './empty.js'); - } - - assert.deepEqual((await run(b, null, {require: false})).output, 123); - }, - ); - - it.v2( - 'supports deferring unused ES6 re-exports (reexport named used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/b.js', - ), - options, - ); - - if (usesSymbolPropagation) { - assert(!findAsset(b, 'message1.js')); - assert(!findAsset(b, 'message3.js')); - } + ]); let calls = []; let res = await run( b, { + output: null, sideEffect: caller => { calls.push(caller); }, }, {require: false}, ); + assert.deepEqual(calls, ['b1']); + assert.deepEqual(res.output, 2); + }); + } - assert.deepEqual( - calls, - shouldScopeHoist ? ['message2'] : ['message2', 'index'], - ); - assert.deepEqual(res.output, 'Message 2'); - }, - ); - - it.v2( - 'supports deferring unused ES6 re-exports (namespace rename used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/c.js', - ), - options, - ); + it('supports deferring unused ES6 re-exports (namespace used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/a.js', + ), + options, + ); - assertBundles(b, [ - { - type: 'js', - assets: usesSymbolPropagation - ? ['c.js', 'message3.js'] - : ['c.js', 'esmodule-helpers.js', 'index.js', 'message3.js'], - }, - ]); + assertBundles(b, [ + { + type: 'js', + assets: usesSymbolPropagation + ? ['a.js', 'message1.js'] + : ['a.js', 'esmodule-helpers.js', 'index.js', 'message1.js'], + }, + ]); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'message1.js')); - } + if (usesSymbolPropagation) { + // TODO this only excluded, but should be deferred. + assert(!findAsset(b, 'message3.js')); + } - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); - - assert.deepEqual( - calls, - shouldScopeHoist ? ['message3'] : ['message3', 'index'], - ); - assert.deepEqual(res.output, {default: 'Message 3'}); - }, - ); + }, + {require: false}, + ); - it.v2( - 'supports deferring unused ES6 re-exports (direct export used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/d.js', - ), - options, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['message1'] : ['message1', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); - assertDependencyWasExcluded(b, 'index.js', './message2.js'); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'message1.js')); - assert(!findAsset(b, 'message3.js')); - } + it('supports deferring an unused ES6 re-export (wildcard, empty, unused)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-all-empty/a.js', + ), + options, + ); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, - ); + if (usesSymbolPropagation) { + assertDependencyWasExcluded(b, 'index.js', './empty.js'); + } - assert.deepEqual(calls, ['index']); - assert.deepEqual(res.output, 'Message 4'); - }, - ); + assert.deepEqual((await run(b, null, {require: false})).output, 123); + }); - it.v2('supports chained ES6 re-exports', async function () { + it('supports deferring unused ES6 re-exports (reexport named used)', async function () { let b = await bundle( path.join( __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-chained/index.js', + '/integration/scope-hoisting/es6/side-effects-re-exports/b.js', ), options, ); if (usesSymbolPropagation) { - assert(!findAsset(b, 'bar.js')); + assert(!findAsset(b, 'message1.js')); + assert(!findAsset(b, 'message3.js')); } let calls = []; @@ -5527,177 +5120,260 @@ describe('javascript', function () { {require: false}, ); - if (shouldScopeHoist) { - try { - assert.deepEqual(calls, ['key', 'foo', 'index']); - } catch (e) { - // A different dependency order, but this is deemed acceptable as it's sideeffect free - assert.deepEqual(calls, ['foo', 'key', 'index']); - } - } else { - assert.deepEqual(calls, ['key', 'foo', 'types', 'index']); - } - - assert.deepEqual(res.output, ['key', 'foo']); + assert.deepEqual( + calls, + shouldScopeHoist ? ['message2'] : ['message2', 'index'], + ); + assert.deepEqual(res.output, 'Message 2'); }); - it.v2( - 'should not optimize away an unused ES6 re-export and an used import', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-import/a.js', - ), - options, + it('supports deferring unused ES6 re-exports (namespace rename used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/c.js', + ), + options, + ); + + assertBundles(b, [ + { + type: 'js', + assets: usesSymbolPropagation + ? ['c.js', 'message3.js'] + : ['c.js', 'esmodule-helpers.js', 'index.js', 'message3.js'], + }, + ]); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'message1.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['message3'] : ['message3', 'index'], + ); + assert.deepEqual(res.output, {default: 'Message 3'}); + }); + + it('supports deferring unused ES6 re-exports (direct export used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/d.js', + ), + options, + ); + + assertDependencyWasExcluded(b, 'index.js', './message2.js'); + if (usesSymbolPropagation) { + assert(!findAsset(b, 'message1.js')); + assert(!findAsset(b, 'message3.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual(calls, ['index']); + assert.deepEqual(res.output, 'Message 4'); + }); + + it('supports chained ES6 re-exports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-chained/index.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'bar.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + if (shouldScopeHoist) { + try { + assert.deepEqual(calls, ['key', 'foo', 'index']); + } catch (e) { + // A different dependency order, but this is deemed acceptable as it's sideeffect free + assert.deepEqual(calls, ['foo', 'key', 'index']); + } + } else { + assert.deepEqual(calls, ['key', 'foo', 'types', 'index']); + } + + assert.deepEqual(res.output, ['key', 'foo']); + }); + + it('should not optimize away an unused ES6 re-export and an used import', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-import/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 123); + }); + + it('should not optimize away an unused ES6 re-export and an used import (different symbols)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-import-different/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 123); + }); + + it('correctly handles ES6 re-exports in library mode entries', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-library/a.js', + ), + options, + ); + + let contents = await outputFS.readFile( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-library/build.js', + ), + 'utf8', + ); + assert(!contents.includes('console.log')); + + let res = await run(b); + assert.deepEqual(res, {c1: 'foo'}); + }); + + if (shouldScopeHoist) { + it('correctly updates deferred assets that are reexported', async function () { + let testDir = path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-update-deferred-reexported', ); - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, 123); - }, - ); + let b = bundler(path.join(testDir, 'index.js'), { + inputFS: overlayFS, + outputFS: overlayFS, + ...options, + }); - it.v2( - 'should not optimize away an unused ES6 re-export and an used import (different symbols)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-import-different/a.js', - ), - options, + let subscription = await b.watch(); + + let bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + let output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '12345hello'); + + await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); + await overlayFS.copyFile( + path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), + path.join(testDir, 'node_modules', 'foo', 'foo.js'), ); - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, 123); - }, - ); + bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '1234556789'); + + await subscription.unsubscribe(); + }); + + it('correctly updates deferred assets that are reexported and imported directly', async function () { + let testDir = path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-update-deferred-direct', + ); - it.v2( - 'correctly handles ES6 re-exports in library mode entries', - async function () { + let b = bundler(path.join(testDir, 'index.js'), { + inputFS: overlayFS, + outputFS: overlayFS, + ...options, + }); + + let subscription = await b.watch(); + + let bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + let output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '12345hello'); + + await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); + await overlayFS.copyFile( + path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), + path.join(testDir, 'node_modules', 'foo', 'foo.js'), + ); + + bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '1234556789'); + + await subscription.unsubscribe(); + }); + + it('removes deferred reexports when imported from multiple asssets', async function () { let b = await bundle( path.join( __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-library/a.js', + '/integration/scope-hoisting/es6/side-effects-re-exports-multiple-dynamic/a.js', ), options, ); let contents = await outputFS.readFile( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-library/build.js', - ), + b.getBundles()[0].filePath, 'utf8', ); - assert(!contents.includes('console.log')); - - let res = await run(b); - assert.deepEqual(res, {c1: 'foo'}); - }, - ); - - if (shouldScopeHoist) { - it.v2( - 'correctly updates deferred assets that are reexported', - async function () { - let testDir = path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-update-deferred-reexported', - ); - - let b = bundler(path.join(testDir, 'index.js'), { - inputFS: overlayFS, - outputFS: overlayFS, - ...options, - }); - - let subscription = await b.watch(); - - let bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - let output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '12345hello'); - - await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); - await overlayFS.copyFile( - path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), - path.join(testDir, 'node_modules', 'foo', 'foo.js'), - ); - - bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '1234556789'); - - await subscription.unsubscribe(); - }, - ); - it.v2( - 'correctly updates deferred assets that are reexported and imported directly', - async function () { - let testDir = path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-update-deferred-direct', - ); - - let b = bundler(path.join(testDir, 'index.js'), { - inputFS: overlayFS, - outputFS: overlayFS, - ...options, - }); - - let subscription = await b.watch(); - - let bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - let output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '12345hello'); - - await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); - await overlayFS.copyFile( - path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), - path.join(testDir, 'node_modules', 'foo', 'foo.js'), - ); - - bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '1234556789'); - - await subscription.unsubscribe(); - }, - ); + assert(!contents.includes('$import$')); + assert(/=\s*1234/.test(contents)); + assert(!/=\s*5678/.test(contents)); - it.v2( - 'removes deferred reexports when imported from multiple asssets', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-multiple-dynamic/a.js', - ), - options, - ); - - let contents = await outputFS.readFile( - b.getBundles()[0].filePath, - 'utf8', - ); - - assert(!contents.includes('$import$')); - assert(/=\s*1234/.test(contents)); - assert(!/=\s*5678/.test(contents)); - - let output = await run(b); - assert.deepEqual(output, [1234, {default: 1234}]); - }, - ); + let output = await run(b); + assert.deepEqual(output, [1234, {default: 1234}]); + }); } - it.v2('keeps side effects by default', async function () { + it('keeps side effects by default', async function () { let b = await bundle( path.join( __dirname, @@ -5721,34 +5397,31 @@ describe('javascript', function () { assert.deepEqual(res.output, 4); }); - it.v2( - 'supports the package.json sideEffects: false flag', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false/a.js', - ), - options, - ); + it('supports the package.json sideEffects: false flag', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false/a.js', + ), + options, + ); - let called = false; - let res = await run( - b, - { - sideEffect: () => { - called = true; - }, + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; }, - {require: false}, - ); + }, + {require: false}, + ); - assert(!called, 'side effect called'); - assert.deepEqual(res.output, 4); - }, - ); + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 4); + }); - it.v2('supports removing a deferred dependency', async function () { + it('supports removing a deferred dependency', async function () { let testDir = path.join( __dirname, '/integration/scope-hoisting/es6/side-effects-false', @@ -5806,7 +5479,7 @@ describe('javascript', function () { } }); - it.v2('supports wildcards', async function () { + it('supports wildcards', async function () { let b = await bundle( path.join( __dirname, @@ -5831,458 +5504,433 @@ describe('javascript', function () { assert.deepEqual(res.output, 'bar'); }); - it.v2( - 'correctly handles excluded and wrapped reexport assets', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false-wrap-excluded/a.js', - ), - options, - ); - - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, 4); - }, - ); + it('correctly handles excluded and wrapped reexport assets', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false-wrap-excluded/a.js', + ), + options, + ); - it.v2( - 'supports the package.json sideEffects flag with an array', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-array/a.js', - ), - options, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 4); + }); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + it('supports the package.json sideEffects flag with an array', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-array/a.js', + ), + options, + ); + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert(calls.toString() == 'foo', "side effect called for 'foo'"); - assert.deepEqual(res.output, 4); - }, - ); + assert(calls.toString() == 'foo', "side effect called for 'foo'"); + assert.deepEqual(res.output, 4); + }); - it.v2( - 'supports the package.json sideEffects: false flag with shared dependencies', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false-duplicate/a.js', - ), - options, - ); + it('supports the package.json sideEffects: false flag with shared dependencies', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false-duplicate/a.js', + ), + options, + ); - let called = false; - let res = await run( - b, - { - sideEffect: () => { - called = true; - }, + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; }, - {require: false}, - ); + }, + {require: false}, + ); - assert(!called, 'side effect called'); - assert.deepEqual(res.output, 6); - }, - ); + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 6); + }); - it.v2( - 'supports the package.json sideEffects: false flag with shared dependencies and code splitting', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-split/a.js', - ), - options, - ); + it('supports the package.json sideEffects: false flag with shared dependencies and code splitting', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-split/a.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(await res.output, 581); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, 581); + }); - it.v2( - 'supports the package.json sideEffects: false flag with shared dependencies and code splitting II', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-split2/a.js', - ), - options, - ); + it('supports the package.json sideEffects: false flag with shared dependencies and code splitting II', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-split2/a.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(await res.output, [{default: 123, foo: 2}, 581]); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, [{default: 123, foo: 2}, 581]); + }); - it.v2( - 'missing exports should be replaced with an empty object', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/empty-module/a.js', - ), - options, - ); + it('missing exports should be replaced with an empty object', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/empty-module/a.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, {b: {}}); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, {b: {}}); + }); - it.v2( - 'supports namespace imports of theoretically excluded reexporting assets', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/import-namespace-sideEffects/index.js', - ), - options, - ); + it('supports namespace imports of theoretically excluded reexporting assets', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/import-namespace-sideEffects/index.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, {Main: 'main', a: 'foo', b: 'bar'}); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, {Main: 'main', a: 'foo', b: 'bar'}); + }); - it.v2( - 'can import from a different bundle via a re-export', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/re-export-bundle-boundary-side-effects/index.js', - ), - options, - ); + it('can import from a different bundle via a re-export', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/re-export-bundle-boundary-side-effects/index.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(await res.output, ['operational', 'ui']); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, ['operational', 'ui']); + }); - it.v2( - 'supports excluding multiple chained namespace reexports', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-chained-re-exports-multiple/a.js', - ), - options, - ); + it('supports excluding multiple chained namespace reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-chained-re-exports-multiple/a.js', + ), + options, + ); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'symbol1.js')); - } + if (usesSymbolPropagation) { + assert(!findAsset(b, 'symbol1.js')); + } - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual( - calls, - shouldScopeHoist ? ['message1'] : ['message1', 'message'], - ); - assert.deepEqual(res.output, 'Message 1'); - }, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['message1'] : ['message1', 'message'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); - it.v2( - 'supports excluding when doing both exports and reexports', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-export-reexport/a.js', - ), - options, - ); + it('supports excluding when doing both exports and reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-export-reexport/a.js', + ), + options, + ); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'other.js')); - } + if (usesSymbolPropagation) { + assert(!findAsset(b, 'other.js')); + } - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual(calls, ['index']); - assert.deepEqual(res.output, 'Message 1'); - }, - ); + assert.deepEqual(calls, ['index']); + assert.deepEqual(res.output, 'Message 1'); + }); - it.v2( - 'supports deferring with chained renaming reexports', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-chained/a.js', - ), - options, - ); + it('supports deferring with chained renaming reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-chained/a.js', + ), + options, + ); - // assertDependencyWasExcluded(b, 'message.js', './message2'); + // assertDependencyWasExcluded(b, 'message.js', './message2'); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual( - calls, - shouldScopeHoist - ? ['message1'] - : ['message1', 'message', 'index2', 'index'], - ); - assert.deepEqual(res.output, 'Message 1'); - }, - ); + assert.deepEqual( + calls, + shouldScopeHoist + ? ['message1'] + : ['message1', 'message', 'index2', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); - it.v2( - 'supports named and renamed reexports of the same asset (default used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/a.js', - ), - options, - ); + it('supports named and renamed reexports of the same asset (default used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/a.js', + ), + options, + ); - if (usesSymbolPropagation) { - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - } + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), + ); + } - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual( - calls, - shouldScopeHoist ? ['other'] : ['other', 'index'], - ); - assert.deepEqual(res.output, 'bar'); - }, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, 'bar'); + }); - it.v2( - 'supports named and renamed reexports of the same asset (named used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/b.js', - ), - options, - ); + it('supports named and renamed reexports of the same asset (named used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/b.js', + ), + options, + ); - if (usesSymbolPropagation) { - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - } + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), + ); + } - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual( - calls, - shouldScopeHoist ? ['other'] : ['other', 'index'], - ); - assert.deepEqual(res.output, 'bar'); - }, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, 'bar'); + }); - it.v2( - 'supports named and renamed reexports of the same asset (namespace used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same/index.js', - ), - options, - ); + it('supports named and renamed reexports of the same asset (namespace used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same/index.js', + ), + options, + ); - let res = await run(b, null, {require: false}); - assert.deepEqual(res.output, [{value1: 123, value2: 123}, 123, 123]); - }, - ); + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, [{value1: 123, value2: 123}, 123, 123]); + }); + + it('supports reexports via variable declaration (unused)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-var-unused/index.js', + ), + options, + ); + + let res = await run(b, {}, {require: false}); + assert.deepEqual((await res.output).foo, 'foo'); + }); + + it('supports named and namespace exports of the same asset (named used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/a.js', + ), + options, + ); - it.v2( - 'supports reexports via variable declaration (unused)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-var-unused/index.js', - ), - options, + if (usesSymbolPropagation) { + assert(!findAsset(b, 'index.js')); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['default']), ); + } - let res = await run(b, {}, {require: false}); - assert.deepEqual((await res.output).foo, 'foo'); - }, - ); + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); - it.v2( - 'supports named and namespace exports of the same asset (named used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/a.js', - ), - options, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['foo']); + }); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'index.js')); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['default']), - ); - } + it('supports named and namespace exports of the same asset (namespace used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/b.js', + ), + options, + ); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, + if (usesSymbolPropagation) { + assert(!findAsset(b, 'index.js')); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), ); + } - assert.deepEqual( - calls, - shouldScopeHoist ? ['other'] : ['other', 'index'], - ); - assert.deepEqual(res.output, ['foo']); - }, - ); + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); - it.v2( - 'supports named and namespace exports of the same asset (namespace used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/b.js', - ), - options, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['bar']); + }); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'index.js')); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - } + it('supports named and namespace exports of the same asset (both used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/c.js', + ), + options, + ); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, + if (usesSymbolPropagation) { + assert(!findAsset(b, 'index.js')); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['default', 'bar']), ); + } - assert.deepEqual( - calls, - shouldScopeHoist ? ['other'] : ['other', 'index'], - ); - assert.deepEqual(res.output, ['bar']); - }, - ); + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); - it.v2( - 'supports named and namespace exports of the same asset (both used)', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/c.js', - ), - options, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['foo', 'bar']); + }); - if (usesSymbolPropagation) { - assert(!findAsset(b, 'index.js')); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['default', 'bar']), - ); - } + it('supports partially used reexporting index file', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-partially-used/index.js', + ), + options, + ); - let calls = []; - let res = await run( + let calls = []; + let res = ( + await run( b, { sideEffect: caller => { @@ -6290,89 +5938,54 @@ describe('javascript', function () { }, }, {require: false}, - ); - - assert.deepEqual( - calls, - shouldScopeHoist ? ['other'] : ['other', 'index'], - ); - assert.deepEqual(res.output, ['foo', 'bar']); - }, - ); - - it.v2( - 'supports partially used reexporting index file', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-partially-used/index.js', - ), - options, - ); - - let calls = []; - let res = ( - await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, - ) - ).output; + ) + ).output; - let [v, async] = res; + let [v, async] = res; - assert.deepEqual(calls, shouldScopeHoist ? ['b'] : ['b', 'index']); - assert.deepEqual(v, 2); + assert.deepEqual(calls, shouldScopeHoist ? ['b'] : ['b', 'index']); + assert.deepEqual(v, 2); - v = await async(); - assert.deepEqual( - calls, - shouldScopeHoist - ? ['b', 'a', 'index', 'dynamic'] - : ['b', 'index', 'a', 'dynamic'], - ); - assert.deepEqual(v.default, [1, 3]); - }, - ); + v = await async(); + assert.deepEqual( + calls, + shouldScopeHoist + ? ['b', 'a', 'index', 'dynamic'] + : ['b', 'index', 'a', 'dynamic'], + ); + assert.deepEqual(v.default, [1, 3]); + }); - it.v2( - 'supports deferring non-weak dependencies that are not used', - async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-semi-weak/a.js', - ), - options, - ); + it('supports deferring non-weak dependencies that are not used', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-semi-weak/a.js', + ), + options, + ); - // assertDependencyWasExcluded(b, 'esm2.js', './other.js'); + // assertDependencyWasExcluded(b, 'esm2.js', './other.js'); - let calls = []; - let res = await run( - b, - { - sideEffect: caller => { - calls.push(caller); - }, + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); }, - {require: false}, - ); + }, + {require: false}, + ); - assert.deepEqual( - calls, - shouldScopeHoist ? ['esm1'] : ['esm1', 'index'], - ); - assert.deepEqual(res.output, 'Message 1'); - }, - ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['esm1'] : ['esm1', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); - it.v2('supports excluding CommonJS (CommonJS unused)', async function () { + it('supports excluding CommonJS (CommonJS unused)', async function () { let b = await bundle( path.join( __dirname, @@ -6415,7 +6028,7 @@ describe('javascript', function () { assert.deepEqual(res.output, 'Message 1'); }); - it.v2('supports excluding CommonJS (CommonJS used)', async function () { + it('supports excluding CommonJS (CommonJS used)', async function () { let b = await bundle( path.join( __dirname, @@ -6452,81 +6065,65 @@ describe('javascript', function () { }); }); - it.v2( - `ignores missing unused import specifiers in source assets ${ - shouldScopeHoist ? 'with' : 'without' - } scope-hoisting`, - async function () { - let b = await bundle( - path.join(__dirname, 'integration/js-unused-import-specifier/a.js'), - options, - ); - let res = await run(b, null, {require: false}); - assert.equal(res.output, 123); - }, - ); + it(`ignores missing unused import specifiers in source assets ${ + shouldScopeHoist ? 'with' : 'without' + } scope-hoisting`, async function () { + let b = await bundle( + path.join(__dirname, 'integration/js-unused-import-specifier/a.js'), + options, + ); + let res = await run(b, null, {require: false}); + assert.equal(res.output, 123); + }); - it.v2( - `ignores missing unused import specifiers in node-modules ${ - shouldScopeHoist ? 'with' : 'without' - } scope-hoisting`, - async function () { - let b = await bundle( + it(`ignores missing unused import specifiers in node-modules ${ + shouldScopeHoist ? 'with' : 'without' + } scope-hoisting`, async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/js-unused-import-specifier-node-modules/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.equal(res.output, 123); + }); + + it(`duplicate assets should share module scope ${ + shouldScopeHoist ? 'with' : 'without' + } scope-hoisting`, async function () { + let b = await bundle( + [ path.join( __dirname, - '/integration/js-unused-import-specifier-node-modules/a.js', + '/integration/scope-hoisting/es6/multi-entry-duplicates/one.js', ), - options, - ); - - let res = await run(b, null, {require: false}); - assert.equal(res.output, 123); - }, - ); - - it.v2( - `duplicate assets should share module scope ${ - shouldScopeHoist ? 'with' : 'without' - } scope-hoisting`, - async function () { - let b = await bundle( - [ - path.join( - __dirname, - '/integration/scope-hoisting/es6/multi-entry-duplicates/one.js', - ), - path.join( - __dirname, - '/integration/scope-hoisting/es6/multi-entry-duplicates/two.js', - ), - ], - options, - ); + path.join( + __dirname, + '/integration/scope-hoisting/es6/multi-entry-duplicates/two.js', + ), + ], + options, + ); - let result = await runBundle( - b, - b.getBundles()[0], - {}, - {require: false}, - ); + let result = await runBundle(b, b.getBundles()[0], {}, {require: false}); - assert.equal(await result.output, 2); - }, - ); + assert.equal(await result.output, 2); + }); - it.v2( - `should work correctly with export called hasOwnProperty ${ - shouldScopeHoist ? 'with' : 'without' - } scope-hoisting`, - async () => { - await fsFixture(overlayFS, __dirname)` + it(`should work correctly with export called hasOwnProperty ${ + shouldScopeHoist ? 'with' : 'without' + } scope-hoisting`, async () => { + await fsFixture(overlayFS, __dirname)` js-export-all-hasOwnProperty a.js: export function hasOwnProperty() { throw new Error("Shouldn't be called"); } b.js: - module.exports = { other: 123 }; + module.exports = { other: 123 } library.js: export * from './a'; @@ -6536,16 +6133,15 @@ describe('javascript', function () { import * as x from './library'; output = sideEffectNoop(x).other;`; - let b = await bundle( - path.join(__dirname, 'js-export-all-hasOwnProperty/index.js'), - { - ...options, - inputFS: overlayFS, - }, - ); - let res = await run(b, null, {require: false}); - assert.equal(res.output, 123); - }, - ); + let b = await bundle( + path.join(__dirname, 'js-export-all-hasOwnProperty/index.js'), + { + ...options, + inputFS: overlayFS, + }, + ); + let res = await run(b, null, {require: false}); + assert.equal(res.output, 123); + }); } }); diff --git a/packages/core/integration-tests/test/json-reporter.js b/packages/core/integration-tests/test/json-reporter.js index cb4c17aba05..a3d2aa2e47a 100644 --- a/packages/core/integration-tests/test/json-reporter.js +++ b/packages/core/integration-tests/test/json-reporter.js @@ -5,7 +5,7 @@ import assert from 'assert'; import invariant from 'assert'; import path from 'path'; -import {bundle, describe, it} from '@parcel/test-utils'; +import {bundle} from '@parcel/test-utils'; import sinon from 'sinon'; const config = path.join( @@ -13,7 +13,7 @@ const config = path.join( './integration/custom-configs/.parcelrc-json-reporter', ); -describe.v2('json reporter', () => { +describe('json reporter', () => { it('logs bundling a commonjs bundle to stdout as json', async () => { let consoleStub = sinon.stub(console, 'log'); try { diff --git a/packages/core/integration-tests/test/kotlin.js b/packages/core/integration-tests/test/kotlin.js index 7a0603dfcb2..0fcee188a60 100644 --- a/packages/core/integration-tests/test/kotlin.js +++ b/packages/core/integration-tests/test/kotlin.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import {assertBundleTree, bundle, describe, it, run} from '@parcel/test-utils'; +import {assertBundleTree, bundle, run} from '@parcel/test-utils'; import commandExists from 'command-exists'; describe.skip('kotlin', function () { diff --git a/packages/core/integration-tests/test/lazy-compile.js b/packages/core/integration-tests/test/lazy-compile.js index 4a41a7f3302..6f72b57d0cf 100644 --- a/packages/core/integration-tests/test/lazy-compile.js +++ b/packages/core/integration-tests/test/lazy-compile.js @@ -2,8 +2,6 @@ import assert from 'assert'; import path from 'path'; import { bundler, - describe, - it, outputFS, distDir, getNextBuild, @@ -49,7 +47,7 @@ const distDirIncludes = async matches => { return true; }; -describe.v2('lazy compile', function () { +describe('lazy compile', function () { it('should lazy compile', async function () { const b = await bundler( path.join(__dirname, '/integration/lazy-compile/index.js'), diff --git a/packages/core/integration-tests/test/less.js b/packages/core/integration-tests/test/less.js index 7bc7658eada..f37e77aeef9 100644 --- a/packages/core/integration-tests/test/less.js +++ b/packages/core/integration-tests/test/less.js @@ -3,15 +3,13 @@ import path from 'path'; import { assertBundles, bundle, - describe, distDir, - it, outputFS, run, } from '@parcel/test-utils'; import {md} from '@parcel/diagnostic'; -describe.v2('less', function () { +describe('less', function () { it('should support requiring less files', async function () { let b = await bundle(path.join(__dirname, '/integration/less/index.js')); diff --git a/packages/core/integration-tests/test/library-bundler.js b/packages/core/integration-tests/test/library-bundler.js index eb22c3ebb10..7c74ccdfe0d 100644 --- a/packages/core/integration-tests/test/library-bundler.js +++ b/packages/core/integration-tests/test/library-bundler.js @@ -3,8 +3,6 @@ import assert from 'assert'; import path from 'path'; import { bundle, - describe, - it, run, runBundle, overlayFS, @@ -14,7 +12,7 @@ import { } from '@parcel/test-utils'; import nullthrows from 'nullthrows'; -describe.v2('library bundler', function () { +describe('library bundler', function () { let count = 0; let dir; beforeEach(async () => { diff --git a/packages/core/integration-tests/test/macros.js b/packages/core/integration-tests/test/macros.js index 47be2dcd917..e0f40b6894f 100644 --- a/packages/core/integration-tests/test/macros.js +++ b/packages/core/integration-tests/test/macros.js @@ -5,15 +5,13 @@ import path from 'path'; import { bundle, bundler, - describe, - it, run, overlayFS, fsFixture, getNextBuild, } from '@parcel/test-utils'; -describe.v2('macros', function () { +describe('macros', function () { let count = 0; let dir; beforeEach(async () => { diff --git a/packages/core/integration-tests/test/markdown.js b/packages/core/integration-tests/test/markdown.js index 34df7c1ef23..4b26395da46 100644 --- a/packages/core/integration-tests/test/markdown.js +++ b/packages/core/integration-tests/test/markdown.js @@ -1,12 +1,6 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundleTree, - bundle, - describe, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundleTree, bundle, outputFS} from '@parcel/test-utils'; describe.skip('markdown', function () { it('should support bundling Markdown', async function () { diff --git a/packages/core/integration-tests/test/mdx.js b/packages/core/integration-tests/test/mdx.js index 13becb7fc24..585581d7770 100644 --- a/packages/core/integration-tests/test/mdx.js +++ b/packages/core/integration-tests/test/mdx.js @@ -1,8 +1,8 @@ const assert = require('assert'); const path = require('path'); -const {bundle, describe, it, run} = require('@parcel/test-utils'); +const {bundle, run} = require('@parcel/test-utils'); -describe.v2('mdx', function () { +describe('mdx', function () { it('should support bundling MDX', async function () { let b = await bundle(path.join(__dirname, '/integration/mdx/index.mdx')); diff --git a/packages/core/integration-tests/test/metrics-reporter.js b/packages/core/integration-tests/test/metrics-reporter.js index 78cc602b8c7..e1115fb514c 100644 --- a/packages/core/integration-tests/test/metrics-reporter.js +++ b/packages/core/integration-tests/test/metrics-reporter.js @@ -2,14 +2,14 @@ import assert from 'assert'; import path from 'path'; -import {bundler, describe, it, outputFS} from '@parcel/test-utils'; +import {bundler, outputFS} from '@parcel/test-utils'; const config = path.join( __dirname, './integration/custom-configs/.parcelrc-build-metrics', ); -describe.v2('Build Metrics Reporter', () => { +describe('Build Metrics Reporter', () => { it('Should dump bundle metrics to parcel-metrics.json', async () => { let b = bundler(path.join(__dirname, '/integration/commonjs/index.js'), { config, diff --git a/packages/core/integration-tests/test/monorepos.js b/packages/core/integration-tests/test/monorepos.js index f839c443071..000589a88ca 100644 --- a/packages/core/integration-tests/test/monorepos.js +++ b/packages/core/integration-tests/test/monorepos.js @@ -4,9 +4,7 @@ import { bundle, bundler, assertBundles, - describe, inputFS, - it, outputFS, fsFixture, ncp, @@ -18,7 +16,7 @@ import { const distDir = path.join(__dirname, '/integration/monorepo/dist/default'); -describe.v2('monorepos', function () { +describe('monorepos', function () { beforeEach(async () => { await outputFS.rimraf(path.join(__dirname, '/monorepo')); }); diff --git a/packages/core/integration-tests/test/namer.js b/packages/core/integration-tests/test/namer.js index d9b0b3155b7..3baf808b474 100644 --- a/packages/core/integration-tests/test/namer.js +++ b/packages/core/integration-tests/test/namer.js @@ -1,8 +1,8 @@ import assert from 'assert'; import path from 'path'; -import {bundle, describe, it, outputFS, distDir} from '@parcel/test-utils'; +import {bundle, outputFS, distDir} from '@parcel/test-utils'; -describe.v2('namer', function () { +describe('namer', function () { it('should determine correct entry root when building a directory', async function () { await bundle(path.join(__dirname, 'integration/namer-dir')); diff --git a/packages/core/integration-tests/test/output-formats.js b/packages/core/integration-tests/test/output-formats.js index 8a25f70d90b..b86e43255e9 100644 --- a/packages/core/integration-tests/test/output-formats.js +++ b/packages/core/integration-tests/test/output-formats.js @@ -6,8 +6,6 @@ import { assertBundles, assertESMExports, bundle as _bundle, - describe, - it, mergeParcelOptions, outputFS, run, @@ -31,7 +29,7 @@ const bundle = (name, opts = {}) => { ); }; -describe.v2('output formats', function () { +describe('output formats', function () { describe('commonjs', function () { it('should support commonjs output (exports)', async function () { let b = await bundle( diff --git a/packages/core/integration-tests/test/packager.js b/packages/core/integration-tests/test/packager.js index 279f37c245d..8768e77c869 100644 --- a/packages/core/integration-tests/test/packager.js +++ b/packages/core/integration-tests/test/packager.js @@ -3,8 +3,6 @@ import path from 'path'; import nullthrows from 'nullthrows'; import { bundle as _bundle, - describe, - it, mergeParcelOptions, overlayFS, } from '@parcel/test-utils'; @@ -23,7 +21,7 @@ function hasPolyfill(code) { return code.includes(polyfill) && !code.includes(noPolyfill); } -describe.v2('packager', function () { +describe('packager', function () { describe('globalThis polyfill', function () { it('should exclude globalThis polyfill in modern builds', async function () { const entryPoint = path.join( diff --git a/packages/core/integration-tests/test/parcel-link.js b/packages/core/integration-tests/test/parcel-link.js index 6ad3df93454..dae99f8e316 100644 --- a/packages/core/integration-tests/test/parcel-link.js +++ b/packages/core/integration-tests/test/parcel-link.js @@ -3,7 +3,7 @@ import type {ProgramOptions} from '@parcel/link'; import {createProgram as _createProgram} from '@parcel/link'; -import {describe, fsFixture, it, overlayFS} from '@parcel/test-utils'; +import {fsFixture, overlayFS} from '@parcel/test-utils'; import assert from 'assert'; import path from 'path'; @@ -19,7 +19,7 @@ function createProgram(opts: ProgramOptions) { return cli; } -describe.v2('@parcel/link', () => { +describe('@parcel/link', () => { let _cwd; let _stdout; diff --git a/packages/core/integration-tests/test/parcel-query.js b/packages/core/integration-tests/test/parcel-query.js index 3f78611a30d..41f78200c9d 100644 --- a/packages/core/integration-tests/test/parcel-query.js +++ b/packages/core/integration-tests/test/parcel-query.js @@ -1,10 +1,10 @@ // @flow import assert from 'assert'; import path from 'path'; -import {bundle, describe, fsFixture, overlayFS} from '@parcel/test-utils'; +import {bundle, fsFixture, overlayFS} from '@parcel/test-utils'; import {loadGraphs} from '../../../dev/query/src'; -describe.v2('parcel-query', () => { +describe('parcel-query', () => { it('loadGraphs', async function () { let entries = 'index.js'; let options = { diff --git a/packages/core/integration-tests/test/parcel-v3.js b/packages/core/integration-tests/test/parcel-v3.js deleted file mode 100644 index 1160ad37276..00000000000 --- a/packages/core/integration-tests/test/parcel-v3.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow - -import {join} from 'path'; - -import {ParcelV3, toFileSystemV3} from '@parcel/core'; -import {NodePackageManager} from '@parcel/package-manager'; -import {describe, fsFixture, inputFS, it, overlayFS} from '@parcel/test-utils'; - -describe('ParcelV3', function () { - it('builds', async () => { - await fsFixture(overlayFS, __dirname)` - index.js: - console.log('hello world'); - - .parcelrc: - { - "extends": "@parcel/config-default", - "transformers": { - "*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": ["@parcel/transformer-js"] - } - } - - yarn.lock: {} - `; - - let parcel = new ParcelV3({ - corePath: '', - entries: [join(__dirname, 'index.js')], - fs: toFileSystemV3(overlayFS), - nodeWorkers: 1, - packageManager: new NodePackageManager(inputFS, __dirname), - }); - - await parcel.build(); - }); -}); diff --git a/packages/core/integration-tests/test/parser.js b/packages/core/integration-tests/test/parser.js index 9dbee25e51e..2ef3c642309 100644 --- a/packages/core/integration-tests/test/parser.js +++ b/packages/core/integration-tests/test/parser.js @@ -1,12 +1,6 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundleTree, - bundle, - describe, - inputFS as fs, - it, -} from '@parcel/test-utils'; +import {assertBundleTree, bundle, inputFS as fs} from '@parcel/test-utils'; describe.skip('parser', function () { it('should support case-insensitive file extension', async function () { diff --git a/packages/core/integration-tests/test/plugin.js b/packages/core/integration-tests/test/plugin.js index 5c23c12856b..dfd4f97f737 100644 --- a/packages/core/integration-tests/test/plugin.js +++ b/packages/core/integration-tests/test/plugin.js @@ -8,11 +8,9 @@ import { assertBundles, bundle, bundler, - describe, distDir, findAsset, getNextBuild, - it, outputFS as fs, overlayFS, run, @@ -20,7 +18,7 @@ import { import * as wasmmap from 'wasm-sourcemap'; import {relativePath} from '@parcel/utils'; -describe.v2('plugin', function () { +describe('plugin', function () { it("continue transformer pipeline on type change that doesn't change the pipeline", async function () { await bundle( path.join(__dirname, '/integration/pipeline-type-change/index.ini'), diff --git a/packages/core/integration-tests/test/pnp.js b/packages/core/integration-tests/test/pnp.js index 10b844857fe..e463785ebbd 100644 --- a/packages/core/integration-tests/test/pnp.js +++ b/packages/core/integration-tests/test/pnp.js @@ -2,18 +2,11 @@ import assert from 'assert'; import Module from 'module'; import path from 'path'; import fs from 'fs'; -import { - bundle, - describe, - it, - run, - assertBundles, - inputFS, -} from '@parcel/test-utils'; +import {bundle, run, assertBundles, inputFS} from '@parcel/test-utils'; const ZIPFS = `${path.sep}zipfs`; -describe.v2('pnp', function () { +describe('pnp', function () { it('should defer to the pnp resolution when needed', async function () { let dir = path.join(__dirname, 'integration/pnp-require'); diff --git a/packages/core/integration-tests/test/postcss.js b/packages/core/integration-tests/test/postcss.js index 361bf63cf62..322d34ce89c 100644 --- a/packages/core/integration-tests/test/postcss.js +++ b/packages/core/integration-tests/test/postcss.js @@ -3,8 +3,6 @@ import path from 'path'; import { bundle, bundler, - describe, - it, run, assertBundles, distDir, @@ -19,7 +17,7 @@ import { MockPackageInstaller, } from '@parcel/package-manager'; -describe.v2('postcss', () => { +describe('postcss', () => { it('should build successfully with only postcss-modules config', async () => { let b = await bundle( path.join(__dirname, '/integration/postcss-modules-config/index.js'), diff --git a/packages/core/integration-tests/test/posthtml.js b/packages/core/integration-tests/test/posthtml.js index 595e7737ad4..030b858dfec 100644 --- a/packages/core/integration-tests/test/posthtml.js +++ b/packages/core/integration-tests/test/posthtml.js @@ -2,8 +2,6 @@ import assert from 'assert'; import { bundle, assertBundles, - describe, - it, removeDistDirectory, distDir, inputFS, @@ -17,7 +15,7 @@ import { MockPackageInstaller, } from '@parcel/package-manager'; -describe.v2('posthtml', function () { +describe('posthtml', function () { afterEach(async () => { await removeDistDirectory(); }); diff --git a/packages/core/integration-tests/test/proxy.js b/packages/core/integration-tests/test/proxy.js index 7121f7c5552..8e851074aa5 100644 --- a/packages/core/integration-tests/test/proxy.js +++ b/packages/core/integration-tests/test/proxy.js @@ -1,7 +1,7 @@ // @flow strict-local import assert from 'assert'; import path from 'path'; -import {bundler, describe, getNextBuild, inputFS, it} from '@parcel/test-utils'; +import {bundler, getNextBuild, inputFS} from '@parcel/test-utils'; import http from 'http'; import getPort from 'get-port'; @@ -47,7 +47,7 @@ function get(file, port, client = http) { }); } -describe.v2('proxy', function () { +describe('proxy', function () { let subscription; let cwd; let server; diff --git a/packages/core/integration-tests/test/pug.js b/packages/core/integration-tests/test/pug.js index 4289c03d5b9..6d3d1c45379 100644 --- a/packages/core/integration-tests/test/pug.js +++ b/packages/core/integration-tests/test/pug.js @@ -1,15 +1,8 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundles, - bundle, - describe, - it, - outputFS, - distDir, -} from '@parcel/test-utils'; - -describe.v2('pug', function () { +import {assertBundles, bundle, outputFS, distDir} from '@parcel/test-utils'; + +describe('pug', function () { it('should support bundling HTML', async function () { const b = await bundle(path.join(__dirname, '/integration/pug/index.pug')); diff --git a/packages/core/integration-tests/test/react-refresh.js b/packages/core/integration-tests/test/react-refresh.js index 953b11f3c3f..d00f711c03d 100644 --- a/packages/core/integration-tests/test/react-refresh.js +++ b/packages/core/integration-tests/test/react-refresh.js @@ -5,9 +5,7 @@ import path from 'path'; import { bundle, bundler, - describe, getNextBuild, - it, overlayFS as fs, sleep, run, @@ -30,7 +28,7 @@ try { } if (MessageChannel) { - describe.v2('react-refresh', function () { + describe('react-refresh', function () { describe('synchronous (automatic runtime)', () => { const testDir = path.join( __dirname, diff --git a/packages/core/integration-tests/test/registered-plugins.js b/packages/core/integration-tests/test/registered-plugins.js index 06f0b499c1e..4b0b41559a0 100644 --- a/packages/core/integration-tests/test/registered-plugins.js +++ b/packages/core/integration-tests/test/registered-plugins.js @@ -1,16 +1,9 @@ // @flow strict-local import assert from 'assert'; import path from 'path'; -import { - bundle, - describe, - it, - run, - overlayFS, - fsFixture, -} from '@parcel/test-utils'; +import {bundle, run, overlayFS, fsFixture} from '@parcel/test-utils'; -describe.v2('plugins with "registered" languages', () => { +describe('plugins with "registered" languages', () => { it('should support plugins with esbuild-register', async () => { const dir = path.join(__dirname, 'esbuild-register-plugin'); overlayFS.mkdirp(dir); diff --git a/packages/core/integration-tests/test/reporters.js b/packages/core/integration-tests/test/reporters.js index f49d85388ab..30cebe31864 100644 --- a/packages/core/integration-tests/test/reporters.js +++ b/packages/core/integration-tests/test/reporters.js @@ -4,9 +4,9 @@ import assert from 'assert'; import {execSync} from 'child_process'; import path from 'path'; -import {bundler, describe, it} from '@parcel/test-utils'; +import {bundler} from '@parcel/test-utils'; -describe.v2('reporters', () => { +describe('reporters', () => { let successfulEntry = path.join( __dirname, 'integration', diff --git a/packages/core/integration-tests/test/resolver.js b/packages/core/integration-tests/test/resolver.js index f4215df1cb7..edfdcb6c5f8 100644 --- a/packages/core/integration-tests/test/resolver.js +++ b/packages/core/integration-tests/test/resolver.js @@ -1,17 +1,9 @@ // @flow strict-local import assert from 'assert'; import path from 'path'; -import { - bundle, - describe, - it, - run, - ncp, - overlayFS, - outputFS, -} from '@parcel/test-utils'; - -describe.v2('resolver', function () { +import {bundle, run, ncp, overlayFS, outputFS} from '@parcel/test-utils'; + +describe('resolver', function () { it('should support resolving tilde in monorepo packages', async function () { let b = await bundle( path.join( diff --git a/packages/core/integration-tests/test/sass.js b/packages/core/integration-tests/test/sass.js index b34586cb232..c2879c33052 100644 --- a/packages/core/integration-tests/test/sass.js +++ b/packages/core/integration-tests/test/sass.js @@ -2,8 +2,6 @@ import assert from 'assert'; import path from 'path'; import { bundle, - describe, - it, run, assertBundles, distDir, @@ -12,7 +10,7 @@ import { fsFixture, } from '@parcel/test-utils'; -describe.v2('sass', function () { +describe('sass', function () { it('should support requiring sass files', async function () { let b = await bundle(path.join(__dirname, '/integration/sass/index.js')); diff --git a/packages/core/integration-tests/test/schema-jsonld.js b/packages/core/integration-tests/test/schema-jsonld.js index 9399654bb0c..c2696397346 100644 --- a/packages/core/integration-tests/test/schema-jsonld.js +++ b/packages/core/integration-tests/test/schema-jsonld.js @@ -1,15 +1,8 @@ -import { - assertBundles, - bundle, - describe, - distDir, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, distDir, outputFS} from '@parcel/test-utils'; import path from 'path'; import assert from 'assert'; -describe.v2('jsonld', function () { +describe('jsonld', function () { it('Should parse a LD+JSON schema and collect dependencies', async function () { let b = await bundle( path.join(__dirname, '/integration/schema-jsonld/index.html'), diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index 7dad4dbbaf3..e6c90afc0c8 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -8,12 +8,10 @@ import { assertBundles, bundle as _bundle, bundler as _bundler, - describe, distDir, findAsset, findDependency, getNextBuild, - it, mergeParcelOptions, outputFS, overlayFS, @@ -52,7 +50,7 @@ const bundler = (name, opts = {}) => { ); }; -describe.v2('scope hoisting', function () { +describe('scope hoisting', function () { describe('es6', function () { it('supports default imports and exports of expressions', async function () { let b = await bundle( diff --git a/packages/core/integration-tests/test/server.js b/packages/core/integration-tests/test/server.js index 6b337b81f6c..6b31b534e2f 100644 --- a/packages/core/integration-tests/test/server.js +++ b/packages/core/integration-tests/test/server.js @@ -5,10 +5,8 @@ import path from 'path'; import { assertBundles, bundler, - describe, getNextBuild, inputFS, - it, outputFS, overlayFS, ncp, @@ -25,7 +23,7 @@ const config = path.join( './integration/custom-configs/.parcelrc-dev-server', ); -describe.v2('server', function () { +describe('server', function () { let subscription; afterEach(async () => { diff --git a/packages/core/integration-tests/test/sourcemaps.js b/packages/core/integration-tests/test/sourcemaps.js index a22caf8b57d..bfd97ad29b3 100644 --- a/packages/core/integration-tests/test/sourcemaps.js +++ b/packages/core/integration-tests/test/sourcemaps.js @@ -6,9 +6,7 @@ import SourceMap from '@parcel/source-map'; import type {InitialParcelOptions} from '@parcel/types'; import { bundle as _bundle, - describe, inputFS, - it, outputFS, overlayFS, shallowEqual, @@ -150,7 +148,7 @@ function checkSourceMapping({ ); } -describe.v2('sourcemaps', function () { +describe('sourcemaps', function () { it('Should create a basic browser sourcemap', async function () { let sourceFilename = path.join( __dirname, diff --git a/packages/core/integration-tests/test/stylus.js b/packages/core/integration-tests/test/stylus.js index 8864ea43036..af1455cb85d 100644 --- a/packages/core/integration-tests/test/stylus.js +++ b/packages/core/integration-tests/test/stylus.js @@ -2,15 +2,13 @@ import assert from 'assert'; import path from 'path'; import { bundle, - describe, - it, run, assertBundles, distDir, outputFS, } from '@parcel/test-utils'; -describe.v2('stylus', function () { +describe('stylus', function () { it('should support requiring stylus files', async function () { let b = await bundle(path.join(__dirname, '/integration/stylus/index.js')); diff --git a/packages/core/integration-tests/test/sugarss.js b/packages/core/integration-tests/test/sugarss.js index b0b444b1111..86bd04c6fe7 100644 --- a/packages/core/integration-tests/test/sugarss.js +++ b/packages/core/integration-tests/test/sugarss.js @@ -1,15 +1,8 @@ import assert from 'assert'; -import { - assertBundles, - bundle, - describe, - distDir, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, distDir, outputFS} from '@parcel/test-utils'; import path from 'path'; -describe.v2('sugarss', function () { +describe('sugarss', function () { it('should correctly parse SugarSS asset', async function () { let b = await bundle( path.join(__dirname, '/integration/sugarss/index.sss'), diff --git a/packages/core/integration-tests/test/svg-react.js b/packages/core/integration-tests/test/svg-react.js index aa7d43992d4..fa4ffff6495 100644 --- a/packages/core/integration-tests/test/svg-react.js +++ b/packages/core/integration-tests/test/svg-react.js @@ -1,8 +1,8 @@ import assert from 'assert'; -import {bundle, describe, it, outputFS} from '@parcel/test-utils'; +import {bundle, outputFS} from '@parcel/test-utils'; import path from 'path'; -describe.v2('svg-react', function () { +describe('svg-react', function () { it('should support transforming SVGs to react components', async function () { let b = await bundle( path.join(__dirname, '/integration/svg-react/react.js'), diff --git a/packages/core/integration-tests/test/svg.js b/packages/core/integration-tests/test/svg.js index 1ac5911b642..6eebd2c937f 100644 --- a/packages/core/integration-tests/test/svg.js +++ b/packages/core/integration-tests/test/svg.js @@ -1,15 +1,8 @@ import assert from 'assert'; -import { - assertBundles, - bundle, - describe, - distDir, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, distDir, outputFS} from '@parcel/test-utils'; import path from 'path'; -describe.v2('svg', function () { +describe('svg', function () { it('should support bundling SVG', async () => { let b = await bundle(path.join(__dirname, '/integration/svg/circle.svg')); diff --git a/packages/core/integration-tests/test/symbol-propagation.js b/packages/core/integration-tests/test/symbol-propagation.js index cadb8b7558f..b7dd47c0be1 100644 --- a/packages/core/integration-tests/test/symbol-propagation.js +++ b/packages/core/integration-tests/test/symbol-propagation.js @@ -1,15 +1,8 @@ import assert from 'assert'; import path from 'path'; -import { - bundler, - describe, - fsFixture, - it, - overlayFS, - run, -} from '@parcel/test-utils'; +import {bundler, fsFixture, overlayFS, run} from '@parcel/test-utils'; -describe.v2('symbol propagation', () => { +describe('symbol propagation', () => { it('should handle removed assets from previous failed builds', async () => { await fsFixture(overlayFS, __dirname)` broken.js: diff --git a/packages/core/integration-tests/test/tailwind-tests.js b/packages/core/integration-tests/test/tailwind-tests.js index 387d2f6a611..56df2d8f53e 100644 --- a/packages/core/integration-tests/test/tailwind-tests.js +++ b/packages/core/integration-tests/test/tailwind-tests.js @@ -1,8 +1,8 @@ import assert from 'assert'; import path from 'path'; -import {bundle, describe, it, outputFS} from '@parcel/test-utils'; +import {bundle, outputFS} from '@parcel/test-utils'; -describe.v2('tailwind', function () { +describe('tailwind', function () { it('should support tailwind from SCSS', async function () { let fixture = path.join(__dirname, '/integration/tailwind-scss'); let b = await bundle(path.join(fixture, 'index.html')); diff --git a/packages/core/integration-tests/test/tracing.js b/packages/core/integration-tests/test/tracing.js index 9db088e6f37..c11f8c0bddf 100644 --- a/packages/core/integration-tests/test/tracing.js +++ b/packages/core/integration-tests/test/tracing.js @@ -1,9 +1,9 @@ // @flow strict-local import assert from 'assert'; import path from 'path'; -import {bundle, describe, distDir, it, outputFS} from '@parcel/test-utils'; +import {bundle, distDir, outputFS} from '@parcel/test-utils'; -describe.v2('tracing', function () { +describe('tracing', function () { it('should produce a trace', async function () { await bundle( path.join(__dirname, '/integration/typescript-jsx/index.tsx'), diff --git a/packages/core/integration-tests/test/transpilation.js b/packages/core/integration-tests/test/transpilation.js index 2b3b44ec7e6..a09ffe3906b 100644 --- a/packages/core/integration-tests/test/transpilation.js +++ b/packages/core/integration-tests/test/transpilation.js @@ -3,10 +3,8 @@ import assert from 'assert'; import path from 'path'; import { bundle, - describe, distDir, inputFS as fs, - it, outputFS, overlayFS, run, @@ -17,7 +15,7 @@ import nullthrows from 'nullthrows'; const inputDir = path.join(__dirname, '/input'); -describe.v2('transpilation', function () { +describe('transpilation', function () { it('should not transpile if no targets are defined', async function () { await bundle(path.join(__dirname, '/integration/babel-default/index.js'), { defaultTargetOptions: { diff --git a/packages/core/integration-tests/test/ts-types.js b/packages/core/integration-tests/test/ts-types.js index f2a5cdfdd0a..07a23ab8508 100644 --- a/packages/core/integration-tests/test/ts-types.js +++ b/packages/core/integration-tests/test/ts-types.js @@ -3,9 +3,7 @@ import path from 'path'; import { assertBundles, bundle, - describe, inputFS, - it, overlayFS, outputFS, ncp, @@ -14,7 +12,7 @@ import { import {md} from '@parcel/diagnostic'; import {normalizeSeparators} from '@parcel/utils'; -describe.v2('typescript types', function () { +describe('typescript types', function () { it('should generate a typescript declaration file', async function () { let b = await bundle( path.join(__dirname, '/integration/ts-types/main/index.ts'), diff --git a/packages/core/integration-tests/test/ts-validation.js b/packages/core/integration-tests/test/ts-validation.js index 1f748052a4d..6c568bd8350 100644 --- a/packages/core/integration-tests/test/ts-validation.js +++ b/packages/core/integration-tests/test/ts-validation.js @@ -3,9 +3,7 @@ import path from 'path'; import { bundle, bundler, - describe, getNextBuild, - it, normalizeFilePath, outputFS, overlayFS, @@ -17,7 +15,7 @@ const config = path.join( './integration/custom-configs/.parcelrc-typescript-validation', ); -describe.v2('ts-validator', function () { +describe('ts-validator', function () { let subscription; afterEach(async () => { if (subscription) { diff --git a/packages/core/integration-tests/test/typescript-tsc.js b/packages/core/integration-tests/test/typescript-tsc.js index 24a424bb323..9c4f8ea3a35 100644 --- a/packages/core/integration-tests/test/typescript-tsc.js +++ b/packages/core/integration-tests/test/typescript-tsc.js @@ -4,16 +4,14 @@ import path from 'path'; import { assertBundles, bundle, - describe, distDir, - it, outputFS, run, } from '@parcel/test-utils'; const config = path.join(__dirname, '/integration/typescript-config/.parcelrc'); -describe.v2('typescript tsc', function () { +describe('typescript tsc', function () { it('should support loading tsconfig.json', async () => { let b = await bundle( path.join(__dirname, '/integration/typescript-config/index.ts'), diff --git a/packages/core/integration-tests/test/typescript.js b/packages/core/integration-tests/test/typescript.js index 389d677b37a..570f7428676 100644 --- a/packages/core/integration-tests/test/typescript.js +++ b/packages/core/integration-tests/test/typescript.js @@ -6,9 +6,7 @@ import nullthrows from 'nullthrows'; import { assertBundles, bundle, - describe, distDir, - it, outputFS, run, } from '@parcel/test-utils'; @@ -18,7 +16,7 @@ const tscConfig = path.join( '/integration/typescript-config/.parcelrc', ); -describe.v2('typescript', function () { +describe('typescript', function () { // This tests both the SWC transformer implementation of typescript (which // powers typescript by default in Parcel) as well as through the Typescript // tsc transformer. Use a `undefined` config to indicate the default config, and the diff --git a/packages/core/integration-tests/test/vue.js b/packages/core/integration-tests/test/vue.js index 32fee9fe8e5..fa81d221634 100644 --- a/packages/core/integration-tests/test/vue.js +++ b/packages/core/integration-tests/test/vue.js @@ -1,8 +1,8 @@ import assert from 'assert'; import path from 'path'; -import {bundle, describe, distDir, it, outputFS, run} from '@parcel/test-utils'; +import {bundle, distDir, outputFS, run} from '@parcel/test-utils'; -describe.v2('vue', function () { +describe('vue', function () { it('should produce a basic vue bundle', async function () { let b = await bundle( path.join(__dirname, '/integration/vue-basic/Basic.vue'), diff --git a/packages/core/integration-tests/test/wasm.js b/packages/core/integration-tests/test/wasm.js index b59a6917117..83a877d3102 100644 --- a/packages/core/integration-tests/test/wasm.js +++ b/packages/core/integration-tests/test/wasm.js @@ -1,13 +1,6 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundleTree, - bundle, - deferred, - describe, - it, - run, -} from '@parcel/test-utils'; +import {assertBundleTree, bundle, deferred, run} from '@parcel/test-utils'; describe.skip('wasm', function () { if (typeof WebAssembly === 'undefined') { diff --git a/packages/core/integration-tests/test/watcher.js b/packages/core/integration-tests/test/watcher.js index 4cf39947c94..2dccb2f24b8 100644 --- a/packages/core/integration-tests/test/watcher.js +++ b/packages/core/integration-tests/test/watcher.js @@ -4,9 +4,7 @@ import path from 'path'; import { assertBundles, bundler, - describe, getNextBuild, - it, run, assertBundleTree, nextBundle, @@ -22,7 +20,7 @@ import {symlinkSync} from 'fs'; const inputDir = path.join(__dirname, '/watcher'); const distDir = path.join(inputDir, 'dist'); -describe.v2('watcher', function () { +describe('watcher', function () { let subscription; afterEach(async () => { if (subscription) { diff --git a/packages/core/integration-tests/test/webextension.js b/packages/core/integration-tests/test/webextension.js index 1ea805665ba..71f72401cd4 100644 --- a/packages/core/integration-tests/test/webextension.js +++ b/packages/core/integration-tests/test/webextension.js @@ -1,15 +1,8 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundles, - bundle, - describe, - distDir, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, distDir, outputFS} from '@parcel/test-utils'; -describe.v2('webextension', function () { +describe('webextension', function () { it('should resolve a full webextension bundle', async function () { let b = await bundle( path.join(__dirname, '/integration/webextension/manifest.json'), diff --git a/packages/core/integration-tests/test/webmanifest.js b/packages/core/integration-tests/test/webmanifest.js index b4f0fc81026..c9e000cf47a 100644 --- a/packages/core/integration-tests/test/webmanifest.js +++ b/packages/core/integration-tests/test/webmanifest.js @@ -1,16 +1,9 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundles, - bundle, - describe, - inputFS, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, inputFS, outputFS} from '@parcel/test-utils'; import {md} from '@parcel/diagnostic'; -describe.v2('webmanifest', function () { +describe('webmanifest', function () { it('should support .webmanifest', async function () { let b = await bundle( path.join(__dirname, '/integration/webmanifest/index.html'), diff --git a/packages/core/integration-tests/test/workers.js b/packages/core/integration-tests/test/workers.js index ed6e3d32d8f..8abaaffda6a 100644 --- a/packages/core/integration-tests/test/workers.js +++ b/packages/core/integration-tests/test/workers.js @@ -3,16 +3,14 @@ import path from 'path'; import { assertBundles, bundle, - describe, inputFS, - it, outputFS, removeDistDirectory, run, runBundle, } from '@parcel/test-utils'; -describe.v2('parcel', function () { +describe('parcel', function () { beforeEach(async () => { await removeDistDirectory(); }); diff --git a/packages/core/integration-tests/test/worklets.js b/packages/core/integration-tests/test/worklets.js index 4c5d9740bd8..bb6c68b6e30 100644 --- a/packages/core/integration-tests/test/worklets.js +++ b/packages/core/integration-tests/test/worklets.js @@ -3,14 +3,12 @@ import path from 'path'; import { assertBundles, bundle, - describe, - it, removeDistDirectory, run, runBundle, } from '@parcel/test-utils'; -describe.v2('parcel', function () { +describe('parcel', function () { beforeEach(async () => { await removeDistDirectory(); }); diff --git a/packages/core/integration-tests/test/xml.js b/packages/core/integration-tests/test/xml.js index b5f64fa817e..f725aab868b 100644 --- a/packages/core/integration-tests/test/xml.js +++ b/packages/core/integration-tests/test/xml.js @@ -1,14 +1,8 @@ import assert from 'assert'; import path from 'path'; -import { - assertBundles, - bundle, - describe, - it, - outputFS, -} from '@parcel/test-utils'; +import {assertBundles, bundle, outputFS} from '@parcel/test-utils'; -describe.v2('xml', function () { +describe('xml', function () { it('should transform an atom feed', async function () { let b = await bundle(path.join(__dirname, '/integration/xml/atom.xml'), { defaultTargetOptions: { diff --git a/packages/core/rust/index.js.flow b/packages/core/rust/index.js.flow index 6e1d71b603d..44987b65017 100644 --- a/packages/core/rust/index.js.flow +++ b/packages/core/rust/index.js.flow @@ -1,84 +1,8 @@ // @flow -import type { - Encoding, - FileCreateInvalidation, - FilePath, - InitialParcelOptions, - PackageManager, -} from '@parcel/types'; +import type {FileCreateInvalidation} from '@parcel/types'; declare export var init: void | (() => void); -export type Transferable = {||}; - -export type ProjectPath = any; -export interface ConfigRequest { - id: string; - invalidateOnFileChange: Array; - invalidateOnConfigKeyChange: Array; - invalidateOnFileCreate: Array; - invalidateOnEnvChange: Array; - invalidateOnOptionChange: Array; - invalidateOnStartup: boolean; - invalidateOnBuild: boolean; -} -export interface RequestOptions {} - -export interface FileSystem { - canonicalize(path: FilePath): FilePath; - cwd(): FilePath; - isDir(path: FilePath): boolean; - isFile(path: FilePath): boolean; - readFile(path: FilePath, encoding?: Encoding): string; -} - -export type ParcelNapiOptions = {| - fs?: FileSystem, - nodeWorkers?: number, - options: {| - corePath?: string, - // TODO Use Omit when available in flow >0.210.0 - ...$Diff< - InitialParcelOptions, - {| - inputFS: InitialParcelOptions['inputFS'], - outputFS: InitialParcelOptions['outputFS'], - packageManager: InitialParcelOptions['packageManager'], - |}, - >, - |}, - packageManager?: PackageManager, - threads?: number, -|}; - -export type ParcelBuildOptions = {| - registerWorker: (channel: Transferable) => void | Promise, -|}; - -declare export class ParcelNapi { - nodeWorkerCount: number; - constructor(options: ParcelNapiOptions): ParcelNapi; - build(options: ParcelBuildOptions): Promise; - buildAssetGraph(options: ParcelBuildOptions): Promise; - static defaultThreadCount(): number; - testingTempFsReadToString(path: string): string; - testingTempFsIsDir(path: string): boolean; - testingTempFsIsFile(path: string): boolean; - testingRpcPing(): void; -} - -declare export function registerWorker( - channel: Transferable, - worker: any, -): void; - -declare export function initializeMonitoring(): void; -declare export function closeMonitoring(): void; -declare export function napiRunConfigRequest( - configRequest: ConfigRequest, - api: any, - options: any, -): void; declare export function findAncestorFile( filenames: Array, from: string, @@ -99,6 +23,16 @@ export interface JsFileSystemOptions { isDir: string => boolean; includeNodeModules?: boolean | Array | {|[string]: boolean|}; } +export interface FileSystem { + fs?: JsFileSystemOptions, + includeNodeModules?: boolean | Array | {|[string]: boolean|}; + conditions?: number, + moduleDirResolver?: (...args: any[]) => any, + mode: number, + entries?: number, + extensions?: Array, + packageExports: boolean +} export interface ResolveOptions { filename: string; specifierType: string; @@ -148,9 +82,3 @@ declare export class Resolver { resolveAsync(options: ResolveOptions): Promise; getInvalidations(path: string): JsInvalidations; } -declare export class ResolverOld { - constructor(projectRoot: string, options: ResolverOptions): Resolver; - resolve(options: ResolveOptions): ResolveResult; - resolveAsync(options: ResolveOptions): Promise; - getInvalidations(path: string): JsInvalidations; -} diff --git a/packages/core/rust/package.json b/packages/core/rust/package.json index a3c65e56098..af4688362db 100644 --- a/packages/core/rust/package.json +++ b/packages/core/rust/package.json @@ -31,7 +31,7 @@ ], "devDependencies": { "@napi-rs/cli": "^2.15.2", - "napi-wasm": "^1.0.1" + "napi-wasm": "^1.1.2" }, "scripts": { "build": "napi build --platform --cargo-cwd ../../../crates/node-bindings", diff --git a/packages/core/test-utils/src/utils.js b/packages/core/test-utils/src/utils.js index 549437ea176..292a9b375e3 100644 --- a/packages/core/test-utils/src/utils.js +++ b/packages/core/test-utils/src/utils.js @@ -107,8 +107,6 @@ If you don't know how, check here: https://bit.ly/2UmWsbD ); } -export const isParcelV3 = process.env.PARCEL_V3 === 'true'; - export function getParcelOptions( entries: FilePath | Array, opts?: $Shape, @@ -132,9 +130,6 @@ export function getParcelOptions( node: '8', }, }, - featureFlags: { - parcelV3: isParcelV3, - }, }, opts, ); @@ -1265,92 +1260,3 @@ export function request( ); }); } - -// $FlowFixMe -let origDescribe = globalThis.describe; -let parcelVersion: string | void; -export function describe(...args: mixed[]) { - parcelVersion = undefined; - origDescribe.apply(this, args); -} - -describe.only = function (...args: mixed[]) { - parcelVersion = undefined; - origDescribe.only.apply(this, args); -}; - -describe.skip = function (...args: mixed[]) { - parcelVersion = undefined; - origDescribe.skip.apply(this, args); -}; - -describe.v2 = function (...args: mixed[]) { - parcelVersion = 'v2'; - if (!isParcelV3) { - origDescribe.apply(this, args); - } -}; - -describe.v2.only = function (...args: mixed[]) { - parcelVersion = 'v2'; - if (!isParcelV3) { - origDescribe.only.apply(this, args); - } -}; - -describe.v3 = function (...args: mixed[]) { - parcelVersion = 'v3'; - if (isParcelV3) { - origDescribe.apply(this, args); - } -}; - -describe.v3.only = function (...args: mixed[]) { - parcelVersion = 'v3'; - if (isParcelV3) { - origDescribe.only.apply(this, args); - } -}; - -let origIt = globalThis.it; -export function it(...args: mixed[]) { - if ( - parcelVersion == null || - (parcelVersion == 'v2' && !isParcelV3) || - (parcelVersion == 'v3' && isParcelV3) - ) { - origIt.apply(this, args); - } -} - -it.only = function (...args: mixed[]) { - origIt.only.apply(this, args); -}; - -it.skip = function (...args: mixed[]) { - origIt.skip.apply(this, args); -}; - -it.v2 = function (...args: mixed[]) { - if (!isParcelV3) { - origIt.apply(this, args); - } -}; - -it.v2.only = function (...args: mixed[]) { - if (!isParcelV3) { - origIt.only.apply(this, args); - } -}; - -it.v3 = function (...args: mixed[]) { - if (isParcelV3) { - origIt.apply(this, args); - } -}; - -it.v3.only = function (...args: mixed[]) { - if (isParcelV3) { - origIt.only.apply(this, args); - } -}; diff --git a/packages/transformers/js/core/src/collect.rs b/packages/transformers/js/core/src/collect.rs index 1912b72f97e..c02e916d64f 100644 --- a/packages/transformers/js/core/src/collect.rs +++ b/packages/transformers/js/core/src/collect.rs @@ -1,31 +1,23 @@ -use std::collections::HashMap; -use std::collections::HashSet; - -use serde::Deserialize; -use serde::Serialize; -use swc_core::common::sync::Lrc; -use swc_core::common::Mark; -use swc_core::common::Span; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::*; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::utils::stack_size::maybe_grow_default; -use swc_core::ecma::visit::noop_visit_type; -use swc_core::ecma::visit::Visit; -use swc_core::ecma::visit::VisitWith; - -use crate::id; -use crate::utils::is_unresolved; -use crate::utils::match_export_name; -use crate::utils::match_export_name_ident; -use crate::utils::match_import; -use crate::utils::match_member_expr; -use crate::utils::match_property_name; -use crate::utils::match_require; -use crate::utils::Bailout; -use crate::utils::BailoutReason; -use crate::utils::SourceLocation; +use std::collections::{HashMap, HashSet}; + +use serde::{Deserialize, Serialize}; +use swc_core::{ + common::{sync::Lrc, Mark, Span, DUMMY_SP}, + ecma::{ + ast::*, + atoms::{js_word, JsWord}, + utils::stack_size::maybe_grow_default, + visit::{noop_visit_type, Visit, VisitWith}, + }, +}; + +use crate::{ + id, + utils::{ + is_unresolved, match_export_name, match_export_name_ident, match_import, match_member_expr, + match_property_name, match_require, Bailout, BailoutReason, SourceLocation, + }, +}; macro_rules! collect_visit_fn { ($name:ident, $type:ident) => { diff --git a/packages/transformers/js/core/src/constant_module.rs b/packages/transformers/js/core/src/constant_module.rs index 373add23de0..14fba59ccdf 100644 --- a/packages/transformers/js/core/src/constant_module.rs +++ b/packages/transformers/js/core/src/constant_module.rs @@ -1,16 +1,10 @@ use std::collections::HashSet; -use swc_core::ecma::ast::Decl; -use swc_core::ecma::ast::Expr; -use swc_core::ecma::ast::Lit; -use swc_core::ecma::ast::Module; -use swc_core::ecma::ast::ModuleDecl; -use swc_core::ecma::ast::ModuleItem; -use swc_core::ecma::ast::Stmt; -use swc_core::ecma::ast::VarDeclKind; -use swc_core::ecma::ast::VarDeclarator; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::visit::Visit; +use swc_core::ecma::{ + ast::{Decl, Expr, Lit, Module, ModuleDecl, ModuleItem, Stmt, VarDeclKind, VarDeclarator}, + atoms::JsWord, + visit::Visit, +}; fn is_safe_literal(lit: &Lit) -> bool { matches!( @@ -157,15 +151,13 @@ impl Visit for ConstantModule { #[cfg(test)] mod tests { - use swc_core::common::comments::SingleThreadedComments; - use swc_core::common::sync::Lrc; - use swc_core::common::FileName; - use swc_core::common::Globals; - use swc_core::common::SourceMap; - use swc_core::ecma::parser::lexer::Lexer; - use swc_core::ecma::parser::Parser; - use swc_core::ecma::parser::StringInput; - use swc_core::ecma::visit::VisitWith; + use swc_core::{ + common::{comments::SingleThreadedComments, sync::Lrc, FileName, Globals, SourceMap}, + ecma::{ + parser::{lexer::Lexer, Parser, StringInput}, + visit::VisitWith, + }, + }; use super::*; extern crate indoc; diff --git a/packages/transformers/js/core/src/dependency_collector.rs b/packages/transformers/js/core/src/dependency_collector.rs index 3d176f14949..ce9077f4be4 100644 --- a/packages/transformers/js/core/src/dependency_collector.rs +++ b/packages/transformers/js/core/src/dependency_collector.rs @@ -1,30 +1,25 @@ -use std::collections::hash_map::DefaultHasher; -use std::collections::HashMap; -use std::fmt; -use std::hash::Hash; -use std::hash::Hasher; -use std::path::Path; +use std::{ + collections::{hash_map::DefaultHasher, HashMap}, + fmt, + hash::{Hash, Hasher}, + path::Path, +}; use path_slash::PathBufExt; -use serde::Deserialize; -use serde::Serialize; -use swc_core::common::sync::Lrc; -use swc_core::common::Mark; -use swc_core::common::SourceMap; -use swc_core::common::Span; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::Callee; -use swc_core::ecma::ast::MemberProp; -use swc_core::ecma::ast::{self}; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::utils::stack_size::maybe_grow_default; -use swc_core::ecma::visit::Fold; -use swc_core::ecma::visit::FoldWith; - -use crate::fold_member_expr_skip_prop; -use crate::utils::*; -use crate::Config; +use serde::{Deserialize, Serialize}; +use swc_core::{ + common::{sync::Lrc, Mark, SourceMap, Span, DUMMY_SP}, + ecma::{ + ast::{ + Callee, MemberProp, {self}, + }, + atoms::{js_word, JsWord}, + utils::stack_size::maybe_grow_default, + visit::{Fold, FoldWith}, + }, +}; + +use crate::{fold_member_expr_skip_prop, utils::*, Config}; macro_rules! hash { ($str:expr) => {{ @@ -423,8 +418,7 @@ impl<'a> Fold for DependencyCollector<'a> { } fn fold_call_expr(&mut self, node: ast::CallExpr) -> ast::CallExpr { - use ast::Expr::*; - use ast::Ident; + use ast::{Expr::*, Ident}; let kind = match &node.callee { Callee::Import(_) => DependencyKind::DynamicImport, @@ -1471,8 +1465,10 @@ fn match_worker_type(expr: Option<&ast::ExprOrSpread>) -> (SourceType, Option( context: RunTestContext, diff --git a/packages/transformers/js/core/src/env_replacer.rs b/packages/transformers/js/core/src/env_replacer.rs index 79c25c92f21..0c67efef58d 100644 --- a/packages/transformers/js/core/src/env_replacer.rs +++ b/packages/transformers/js/core/src/env_replacer.rs @@ -1,14 +1,17 @@ -use std::collections::HashMap; -use std::collections::HashSet; -use std::vec; +use std::{ + collections::{HashMap, HashSet}, + vec, +}; use ast::*; -use swc_core::common::sync::Lrc; -use swc_core::common::Mark; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::visit::{VisitMut, VisitMutWith}; +use swc_core::{ + common::{sync::Lrc, Mark, DUMMY_SP}, + ecma::{ + ast, + atoms::JsWord, + visit::{VisitMut, VisitMutWith}, + }, +}; use crate::utils::*; diff --git a/packages/transformers/js/core/src/fs.rs b/packages/transformers/js/core/src/fs.rs index 43b7781b058..807017265e1 100644 --- a/packages/transformers/js/core/src/fs.rs +++ b/packages/transformers/js/core/src/fs.rs @@ -1,24 +1,22 @@ -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use data_encoding::BASE64; -use data_encoding::HEXLOWER; -use swc_core::common::Mark; -use swc_core::common::Span; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::*; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::utils::stack_size::maybe_grow_default; -use swc_core::ecma::visit::Fold; -use swc_core::ecma::visit::FoldWith; -use swc_core::ecma::visit::VisitWith; +use data_encoding::{BASE64, HEXLOWER}; +use swc_core::{ + common::{Mark, Span, DUMMY_SP}, + ecma::{ + ast::*, + atoms::JsWord, + utils::stack_size::maybe_grow_default, + visit::{Fold, FoldWith, VisitWith}, + }, +}; -use crate::collect::Collect; -use crate::collect::Import; -use crate::dependency_collector::DependencyDescriptor; -use crate::dependency_collector::DependencyKind; -use crate::id; -use crate::utils::SourceLocation; +use crate::{ + collect::{Collect, Import}, + dependency_collector::{DependencyDescriptor, DependencyKind}, + id, + utils::SourceLocation, +}; pub fn inline_fs<'a>( filename: &str, diff --git a/packages/transformers/js/core/src/global_replacer.rs b/packages/transformers/js/core/src/global_replacer.rs index c1c70e22a9d..3c458485ce2 100644 --- a/packages/transformers/js/core/src/global_replacer.rs +++ b/packages/transformers/js/core/src/global_replacer.rs @@ -2,25 +2,19 @@ use std::path::Path; use indexmap::IndexMap; use path_slash::PathBufExt; -use swc_core::common::sync::Lrc; -use swc_core::common::Mark; -use swc_core::common::SourceMap; -use swc_core::common::SyntaxContext; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::{self, Expr}; -use swc_core::ecma::ast::{ComputedPropName, Module}; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::visit::VisitMut; -use swc_core::ecma::visit::VisitMutWith; +use swc_core::{ + common::{sync::Lrc, Mark, SourceMap, SyntaxContext, DUMMY_SP}, + ecma::{ + ast::{self, ComputedPropName, Expr, Module}, + atoms::{js_word, JsWord}, + visit::{VisitMut, VisitMutWith}, + }, +}; -use crate::dependency_collector::DependencyDescriptor; -use crate::dependency_collector::DependencyKind; -use crate::utils::create_global_decl_stmt; -use crate::utils::create_require; -use crate::utils::is_unresolved; -use crate::utils::SourceLocation; -use crate::utils::SourceType; +use crate::{ + dependency_collector::{DependencyDescriptor, DependencyKind}, + utils::{create_global_decl_stmt, create_require, is_unresolved, SourceLocation, SourceType}, +}; /// Replaces a few node.js constants with literals or require statements. /// This duplicates some logic in [`NodeReplacer`] @@ -67,9 +61,7 @@ pub struct GlobalReplacer<'a> { impl VisitMut for GlobalReplacer<'_> { fn visit_mut_expr(&mut self, node: &mut Expr) { - use ast::Expr::*; - use ast::MemberExpr; - use ast::MemberProp; + use ast::{Expr::*, MemberExpr, MemberProp}; let Ident(id) = node else { node.visit_mut_children_with(self); @@ -210,9 +202,11 @@ mod test { use swc_core::ecma::atoms::JsWord; - use crate::global_replacer::GlobalReplacer; - use crate::test_utils::{run_visit, RunTestContext, RunVisitResult}; - use crate::{DependencyDescriptor, DependencyKind}; + use crate::{ + global_replacer::GlobalReplacer, + test_utils::{run_visit, RunTestContext, RunVisitResult}, + DependencyDescriptor, DependencyKind, + }; fn make_global_replacer( run_test_context: RunTestContext, diff --git a/packages/transformers/js/core/src/hoist.rs b/packages/transformers/js/core/src/hoist.rs index 27ea16cae5e..5b5b592a7fe 100644 --- a/packages/transformers/js/core/src/hoist.rs +++ b/packages/transformers/js/core/src/hoist.rs @@ -1,39 +1,29 @@ -use std::collections::hash_map::DefaultHasher; -use std::collections::HashMap; -use std::collections::HashSet; -use std::hash::Hasher; +use std::{ + collections::{hash_map::DefaultHasher, HashMap, HashSet}, + hash::Hasher, +}; use indexmap::IndexMap; -use serde::Deserialize; -use serde::Serialize; -use swc_core::common::Mark; -use swc_core::common::Span; -use swc_core::common::SyntaxContext; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::*; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::utils::stack_size::maybe_grow_default; -use swc_core::ecma::visit::Fold; -use swc_core::ecma::visit::FoldWith; - -use crate::collect::Collect; -use crate::collect::Export; -use crate::collect::Import; -use crate::collect::ImportKind; -use crate::id; -use crate::utils::get_undefined_ident; -use crate::utils::is_unresolved; -use crate::utils::match_export_name; -use crate::utils::match_export_name_ident; -use crate::utils::match_import; -use crate::utils::match_member_expr; -use crate::utils::match_property_name; -use crate::utils::match_require; -use crate::utils::CodeHighlight; -use crate::utils::Diagnostic; -use crate::utils::DiagnosticSeverity; -use crate::utils::SourceLocation; +use serde::{Deserialize, Serialize}; +use swc_core::{ + common::{Mark, Span, SyntaxContext, DUMMY_SP}, + ecma::{ + ast::*, + atoms::{js_word, JsWord}, + utils::stack_size::maybe_grow_default, + visit::{Fold, FoldWith}, + }, +}; + +use crate::{ + collect::{Collect, Export, Import, ImportKind}, + id, + utils::{ + get_undefined_ident, is_unresolved, match_export_name, match_export_name_ident, match_import, + match_member_expr, match_property_name, match_require, CodeHighlight, Diagnostic, + DiagnosticSeverity, SourceLocation, + }, +}; macro_rules! hash { ($str:expr) => {{ @@ -1357,20 +1347,15 @@ impl<'a> Hoist<'a> { #[cfg(test)] mod tests { - use swc_core::common::chain; - use swc_core::common::comments::SingleThreadedComments; - use swc_core::common::sync::Lrc; - use swc_core::common::FileName; - use swc_core::common::Globals; - use swc_core::common::SourceMap; - use swc_core::ecma::codegen::text_writer::JsWriter; - use swc_core::ecma::parser::lexer::Lexer; - use swc_core::ecma::parser::Parser; - use swc_core::ecma::parser::StringInput; - use swc_core::ecma::transforms::base::fixer::fixer; - use swc_core::ecma::transforms::base::hygiene::hygiene; - use swc_core::ecma::transforms::base::resolver; - use swc_core::ecma::visit::VisitWith; + use swc_core::{ + common::{chain, comments::SingleThreadedComments, sync::Lrc, FileName, Globals, SourceMap}, + ecma::{ + codegen::text_writer::JsWriter, + parser::{lexer::Lexer, Parser, StringInput}, + transforms::base::{fixer::fixer, hygiene::hygiene, resolver}, + visit::VisitWith, + }, + }; use super::*; use crate::utils::BailoutReason; diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index d6b0fbd4677..03cdbdf4ba4 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -12,83 +12,57 @@ mod test_utils; mod typeof_replacer; mod utils; -use std::collections::HashMap; -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; -use std::str::FromStr; +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, + str::FromStr, +}; -use collect::Collect; pub use collect::CollectImportedSymbol; -use collect::CollectResult; +use collect::{Collect, CollectResult}; use constant_module::ConstantModule; -pub use dependency_collector::dependency_collector; -pub use dependency_collector::DependencyDescriptor; -pub use dependency_collector::DependencyKind; +pub use dependency_collector::{dependency_collector, DependencyDescriptor, DependencyKind}; use env_replacer::*; use fs::inline_fs; use global_replacer::GlobalReplacer; -use hoist::hoist; -pub use hoist::ExportedSymbol; -use hoist::HoistResult; -pub use hoist::ImportedSymbol; +use hoist::{hoist, HoistResult}; +pub use hoist::{ExportedSymbol, ImportedSymbol}; use indexmap::IndexMap; use modules::esm2cjs; use node_replacer::NodeReplacer; -use parcel_macros::MacroCallback; -use parcel_macros::MacroError; -use parcel_macros::Macros; +use parcel_macros::{MacroCallback, MacroError, Macros}; use path_slash::PathExt; -use serde::Deserialize; -use serde::Serialize; -use swc_core::common::chain; -use swc_core::common::comments::SingleThreadedComments; -use swc_core::common::errors::Handler; -use swc_core::common::pass::Optional; -use swc_core::common::source_map::SourceMapGenConfig; -use swc_core::common::sync::Lrc; -use swc_core::common::FileName; -use swc_core::common::Globals; -use swc_core::common::Mark; -use swc_core::common::SourceMap; -use swc_core::ecma::ast::Module; -use swc_core::ecma::ast::ModuleItem; -use swc_core::ecma::ast::Program; -use swc_core::ecma::codegen::text_writer::JsWriter; -use swc_core::ecma::parser::error::Error; -use swc_core::ecma::parser::lexer::Lexer; -use swc_core::ecma::parser::EsSyntax; -use swc_core::ecma::parser::Parser; -use swc_core::ecma::parser::StringInput; -use swc_core::ecma::parser::Syntax; -use swc_core::ecma::parser::TsSyntax; -use swc_core::ecma::preset_env::preset_env; -use swc_core::ecma::preset_env::Mode::Entry; -use swc_core::ecma::preset_env::Targets; -use swc_core::ecma::preset_env::Version; -use swc_core::ecma::preset_env::Versions; -use swc_core::ecma::transforms::base::fixer::fixer; -use swc_core::ecma::transforms::base::fixer::paren_remover; -use swc_core::ecma::transforms::base::helpers; -use swc_core::ecma::transforms::base::hygiene::hygiene; -use swc_core::ecma::transforms::base::resolver; -use swc_core::ecma::transforms::base::Assumptions; -use swc_core::ecma::transforms::compat::reserved_words::reserved_words; -use swc_core::ecma::transforms::optimization::simplify::dead_branch_remover; -use swc_core::ecma::transforms::optimization::simplify::expr_simplifier; -use swc_core::ecma::transforms::proposal::decorators; -use swc_core::ecma::transforms::react; -use swc_core::ecma::transforms::typescript; -use swc_core::ecma::visit::VisitWith; -use swc_core::ecma::visit::{as_folder, FoldWith}; +use serde::{Deserialize, Serialize}; +use swc_core::{ + common::{ + chain, comments::SingleThreadedComments, errors::Handler, pass::Optional, + source_map::SourceMapGenConfig, sync::Lrc, FileName, Globals, Mark, SourceMap, + }, + ecma::{ + ast::{Module, ModuleItem, Program}, + codegen::text_writer::JsWriter, + parser::{error::Error, lexer::Lexer, EsSyntax, Parser, StringInput, Syntax, TsSyntax}, + preset_env::{preset_env, Mode::Entry, Targets, Version, Versions}, + transforms::{ + base::{ + fixer::{fixer, paren_remover}, + helpers, + hygiene::hygiene, + resolver, Assumptions, + }, + compat::reserved_words::reserved_words, + optimization::simplify::{dead_branch_remover, expr_simplifier}, + proposal::decorators, + react, typescript, + }, + visit::{as_folder, FoldWith, VisitWith}, + }, +}; use typeof_replacer::*; -use utils::error_buffer_to_diagnostics; -use utils::CodeHighlight; -use utils::Diagnostic; -use utils::DiagnosticSeverity; -use utils::ErrorBuffer; -pub use utils::SourceLocation; -pub use utils::SourceType; +use utils::{ + error_buffer_to_diagnostics, CodeHighlight, Diagnostic, DiagnosticSeverity, ErrorBuffer, +}; +pub use utils::{SourceLocation, SourceType}; type SourceMapBuffer = Vec<(swc_core::common::BytePos, swc_core::common::LineCol)>; diff --git a/packages/transformers/js/core/src/modules.rs b/packages/transformers/js/core/src/modules.rs index 24cbd5337f2..9bfe84a14b6 100644 --- a/packages/transformers/js/core/src/modules.rs +++ b/packages/transformers/js/core/src/modules.rs @@ -1,25 +1,21 @@ -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use inflector::Inflector; -use swc_core::common::Mark; -use swc_core::common::Span; -use swc_core::common::SyntaxContext; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::*; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::preset_env::Feature; -use swc_core::ecma::preset_env::Versions; -use swc_core::ecma::utils::stack_size::maybe_grow_default; -use swc_core::ecma::visit::Fold; -use swc_core::ecma::visit::FoldWith; - -use crate::fold_member_expr_skip_prop; -use crate::id; -use crate::utils::get_undefined_ident; -use crate::utils::match_export_name; -use crate::utils::match_export_name_ident; +use swc_core::{ + common::{Mark, Span, SyntaxContext, DUMMY_SP}, + ecma::{ + ast::*, + atoms::{js_word, JsWord}, + preset_env::{Feature, Versions}, + utils::stack_size::maybe_grow_default, + visit::{Fold, FoldWith}, + }, +}; + +use crate::{ + fold_member_expr_skip_prop, id, + utils::{get_undefined_ident, match_export_name, match_export_name_ident}, +}; pub fn esm2cjs(node: Module, unresolved_mark: Mark, versions: Option) -> (Module, bool) { let mut fold = ESMFold { diff --git a/packages/transformers/js/core/src/node_replacer.rs b/packages/transformers/js/core/src/node_replacer.rs index e7a1dcfbf5f..74eac104131 100644 --- a/packages/transformers/js/core/src/node_replacer.rs +++ b/packages/transformers/js/core/src/node_replacer.rs @@ -1,24 +1,19 @@ -use std::collections::HashMap; -use std::ffi::OsStr; -use std::path::Path; - -use swc_core::common::sync::Lrc; -use swc_core::common::Mark; -use swc_core::common::SourceMap; -use swc_core::common::SyntaxContext; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast; -use swc_core::ecma::ast::MemberProp; -use swc_core::ecma::atoms::JsWord; -use swc_core::ecma::visit::{VisitMut, VisitMutWith}; - -use crate::dependency_collector::DependencyDescriptor; -use crate::dependency_collector::DependencyKind; -use crate::utils::create_global_decl_stmt; -use crate::utils::create_require; -use crate::utils::is_unresolved; -use crate::utils::SourceLocation; -use crate::utils::SourceType; +use std::{collections::HashMap, ffi::OsStr, path::Path}; + +use swc_core::{ + common::{sync::Lrc, Mark, SourceMap, SyntaxContext, DUMMY_SP}, + ecma::{ + ast, + ast::MemberProp, + atoms::JsWord, + visit::{VisitMut, VisitMutWith}, + }, +}; + +use crate::{ + dependency_collector::{DependencyDescriptor, DependencyKind}, + utils::{create_global_decl_stmt, create_require, is_unresolved, SourceLocation, SourceType}, +}; /// Replaces __filename and __dirname with globals that reference to string literals for the /// file-path of this file. diff --git a/packages/transformers/js/core/src/test_utils.rs b/packages/transformers/js/core/src/test_utils.rs index 58ad248e51d..a34de780ff7 100644 --- a/packages/transformers/js/core/src/test_utils.rs +++ b/packages/transformers/js/core/src/test_utils.rs @@ -1,13 +1,15 @@ -use swc_core::common::input::StringInput; -use swc_core::common::sync::Lrc; -use swc_core::common::util::take::Take; -use swc_core::common::{FileName, Globals, Mark, SourceMap, GLOBALS}; -use swc_core::ecma::ast::Module; -use swc_core::ecma::codegen::text_writer::JsWriter; -use swc_core::ecma::parser::lexer::Lexer; -use swc_core::ecma::parser::Parser; -use swc_core::ecma::transforms::base::resolver; -use swc_core::ecma::visit::{Fold, FoldWith, VisitMut, VisitMutWith}; +use swc_core::{ + common::{ + input::StringInput, sync::Lrc, util::take::Take, FileName, Globals, Mark, SourceMap, GLOBALS, + }, + ecma::{ + ast::Module, + codegen::text_writer::JsWriter, + parser::{lexer::Lexer, Parser}, + transforms::base::resolver, + visit::{Fold, FoldWith, VisitMut, VisitMutWith}, + }, +}; pub(crate) struct RunTestContext { /// Source-map in use @@ -116,8 +118,10 @@ fn run_with_transformation( #[cfg(test)] mod test { - use swc_core::ecma::ast::{Lit, Str}; - use swc_core::ecma::visit::VisitMut; + use swc_core::ecma::{ + ast::{Lit, Str}, + visit::VisitMut, + }; use super::*; diff --git a/packages/transformers/js/core/src/typeof_replacer.rs b/packages/transformers/js/core/src/typeof_replacer.rs index 2121f6cc652..03d5dfc9fad 100644 --- a/packages/transformers/js/core/src/typeof_replacer.rs +++ b/packages/transformers/js/core/src/typeof_replacer.rs @@ -1,10 +1,11 @@ -use swc_core::common::Mark; -use swc_core::ecma::ast::Expr; -use swc_core::ecma::ast::Lit; -use swc_core::ecma::ast::Str; -use swc_core::ecma::ast::UnaryOp; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::visit::{VisitMut, VisitMutWith}; +use swc_core::{ + common::Mark, + ecma::{ + ast::{Expr, Lit, Str, UnaryOp}, + atoms::js_word, + visit::{VisitMut, VisitMutWith}, + }, +}; use crate::utils::is_unresolved; diff --git a/packages/transformers/js/core/src/utils.rs b/packages/transformers/js/core/src/utils.rs index a60628e7ea7..b7e1a32ad36 100644 --- a/packages/transformers/js/core/src/utils.rs +++ b/packages/transformers/js/core/src/utils.rs @@ -1,28 +1,25 @@ use std::cmp::Ordering; -use serde::Deserialize; -use serde::Serialize; -use swc_core::common::errors::DiagnosticBuilder; -use swc_core::common::errors::Emitter; -use swc_core::common::Mark; -use swc_core::common::SourceMap; -use swc_core::common::Span; -use swc_core::common::SyntaxContext; -use swc_core::common::DUMMY_SP; -use swc_core::ecma::ast::Ident; -use swc_core::ecma::ast::{self}; -use swc_core::ecma::atoms::js_word; -use swc_core::ecma::atoms::JsWord; +use serde::{Deserialize, Serialize}; +use swc_core::{ + common::{ + errors::{DiagnosticBuilder, Emitter}, + Mark, SourceMap, Span, SyntaxContext, DUMMY_SP, + }, + ecma::{ + ast::{ + Ident, {self}, + }, + atoms::{js_word, JsWord}, + }, +}; pub fn is_unresolved(ident: &Ident, unresolved_mark: Mark) -> bool { ident.span.ctxt.outer() == unresolved_mark } pub fn match_member_expr(expr: &ast::MemberExpr, idents: Vec<&str>, unresolved_mark: Mark) -> bool { - use ast::Expr; - use ast::Lit; - use ast::MemberProp; - use ast::Str; + use ast::{Expr, Lit, MemberProp, Str}; let mut member = expr; let mut idents = idents; diff --git a/packages/utils/dev-dep-resolver-old/Cargo.toml b/packages/utils/dev-dep-resolver-old/Cargo.toml deleted file mode 100644 index 6d423a9f600..00000000000 --- a/packages/utils/dev-dep-resolver-old/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -authors = ["Devon Govett "] -name = "parcel-dev-dep-resolver-old" -version = "0.1.0" -edition = "2021" - -[dependencies] -parcel-resolver-old = { path = "../node-resolver-rs-old" } -es-module-lexer = { git = "https://github.com/devongovett/es-module-lexer" } -serde_json = "1.0.91" -rayon = "1.7.0" -dashmap = "5.4.0" -glob = "0.3.1" diff --git a/packages/utils/dev-dep-resolver-old/src/lib.rs b/packages/utils/dev-dep-resolver-old/src/lib.rs deleted file mode 100644 index 94e800ef0f2..00000000000 --- a/packages/utils/dev-dep-resolver-old/src/lib.rs +++ /dev/null @@ -1,600 +0,0 @@ -use std::borrow::Cow; -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -use dashmap::DashMap; -use dashmap::DashSet; -use es_module_lexer::lex; -use es_module_lexer::ImportKind; -use parcel_resolver_old::CacheCow; -use parcel_resolver_old::Invalidations; -use parcel_resolver_old::ModuleType; -use parcel_resolver_old::Resolution; -use parcel_resolver_old::ResolveOptions; -use parcel_resolver_old::Resolver; -use parcel_resolver_old::ResolverError; -use parcel_resolver_old::Specifier; -use parcel_resolver_old::SpecifierError; -use parcel_resolver_old::SpecifierType; -// use rayon::prelude::{ParallelBridge, ParallelIterator}; - -#[derive(Debug)] -pub enum EsmGraphBuilderError { - IOError(std::io::Error), - ParseError, - ResolverError(ResolverError), - Dynamic, - PatternError(glob::PatternError), - GlobError(glob::GlobError), - SpecifierError(SpecifierError), -} - -impl From for EsmGraphBuilderError { - fn from(e: std::io::Error) -> Self { - EsmGraphBuilderError::IOError(e) - } -} - -impl From for EsmGraphBuilderError { - fn from(_: usize) -> Self { - EsmGraphBuilderError::ParseError - } -} - -impl From for EsmGraphBuilderError { - fn from(e: ResolverError) -> Self { - EsmGraphBuilderError::ResolverError(e) - } -} - -impl From for EsmGraphBuilderError { - fn from(value: glob::PatternError) -> Self { - EsmGraphBuilderError::PatternError(value) - } -} - -impl From for EsmGraphBuilderError { - fn from(value: glob::GlobError) -> Self { - EsmGraphBuilderError::GlobError(value) - } -} - -impl From for EsmGraphBuilderError { - fn from(value: SpecifierError) -> Self { - EsmGraphBuilderError::SpecifierError(value) - } -} - -#[derive(Default)] -pub struct Cache { - entries: DashMap, -} - -struct EsmGraphBuilder<'a> { - visited: DashSet, - visited_globs: DashSet, - invalidations: Invalidations, - cjs_resolver: Resolver<'a>, - esm_resolver: Resolver<'a>, - cache: &'a Cache, -} - -impl<'a> EsmGraphBuilder<'a> { - pub fn build(&self, file: &Path) -> Result<(), EsmGraphBuilderError> { - if self.visited.contains(file) { - return Ok(()); - } - - self.visited.insert(file.to_owned()); - - if let Some(ext) = file.extension() { - if ext != "js" && ext != "cjs" && ext != "mjs" { - // Ignore. - return Ok(()); - } - } - - if let Some(invalidations) = self.cache.entries.get(file) { - self.invalidations.extend(&invalidations); - for p in invalidations.invalidate_on_file_change.iter() { - self.build(&p)?; - } - return Ok(()); - } - - let invalidations = Invalidations::default(); - let module_type = self - .esm_resolver - .resolve_module_type(file, &invalidations)?; - let resolver = match module_type { - ModuleType::CommonJs | ModuleType::Json => &self.cjs_resolver, - ModuleType::Module => &self.esm_resolver, - }; - let contents = resolver.cache.fs.read_to_string(file)?; - let module = lex(&contents)?; - #[allow(clippy::map_collect_result_unit)] - module - .imports() - // .par_bridge() - .map(|import| -> Result<(), EsmGraphBuilderError> { - match import.kind() { - ImportKind::DynamicExpression => { - if let Some(glob) = specifier_to_glob(&import.specifier()) { - // println!("GLOB {:?} {:?}", import.specifier(), glob); - self.expand_glob(&glob, file, resolver, &invalidations)?; - } else { - // println!("DYNAMIC: {} {:?}", import.specifier(), file); - invalidations.invalidate_on_startup(); - } - } - ImportKind::DynamicString | ImportKind::Standard => { - // Skip flow type imports. - if import.statement().starts_with("import type ") { - return Ok(()); - } - - if let Ok((Resolution::Path(p), _)) = resolver.resolve_with_invalidations( - &import.specifier(), - file, - SpecifierType::Esm, - &invalidations, - ResolveOptions::default(), - ) { - // println!( - // "IMPORT {} {:?} {:?} {:?}", - // import.specifier(), - // import.kind(), - // file, - // p - // ); - invalidations.invalidate_on_file_change(&p); - self.build(&p)?; - } else { - // Ignore dependencies that don't resolve to anything. - // The resolver calls invalidate_on_file_create already. - } - } - ImportKind::Meta => {} - } - - Ok(()) - }) - .collect::>()?; - - self.invalidations.extend(&invalidations); - self.cache.entries.insert(file.to_owned(), invalidations); - Ok(()) - } - - pub fn expand_glob( - &self, - pattern: &str, - from: &Path, - resolver: &Resolver<'a>, - invalidations: &Invalidations, - ) -> Result<(), EsmGraphBuilderError> { - // Parse the specifier. If it is a bare specifier, resolve the package first - // and append the subpath back on to generate the final glob. Otherwise, convert - // the glob to an absolute path. - let specifier = Specifier::parse(pattern, SpecifierType::Esm, resolver.flags)?; - let pattern = match specifier { - (Specifier::Absolute(path), _) => path, - (Specifier::Relative(relative), _) => Cow::Owned(resolve_path(from, relative)), - (Specifier::Package(mut package, subpath), _) => { - // Resolve the package.json file within the package rather than the package entry. - // TODO: how should we handle package exports? - package += "/package.json"; - match resolver.resolve_with_invalidations( - &package, - from, - SpecifierType::Esm, - invalidations, - ResolveOptions::default(), - ) { - Ok((Resolution::Path(p), _)) => Cow::Owned(p.parent().unwrap().join(subpath.as_ref())), - _ => return Ok(()), - } - } - _ => return Ok(()), - }; - - // Invalidate when new files match the glob. - invalidations.invalidate_on_glob_create(pattern.to_string_lossy()); - - if self.visited_globs.contains(pattern.as_ref()) { - return Ok(()); - } - - self.visited_globs.insert(pattern.to_path_buf()); - - for path in glob::glob(pattern.to_string_lossy().as_ref())? { - let path = path?; - invalidations.invalidate_on_file_change(&path); - self.build(&path)?; - } - - Ok(()) - } -} - -/// Attempts to convert a dynamic specifier with string interpolations into a glob. -/// The expression must either start with a string literal with string concatenations, -/// or be a template literal. -fn specifier_to_glob(specifier: &str) -> Option { - let mut bytes = specifier.as_bytes(); - let mut result = String::new(); - while let Some((s, b)) = read_string(bytes) { - if result.ends_with("**") && !s.starts_with('/') { - result.push_str("/*"); - } - result.push_str(&s); - bytes = skip_comments_and_whitespace(b); - if bytes.is_empty() { - break; - } - - if bytes[0] == b'+' { - let mut added = false; - loop { - bytes = skip_comments_and_whitespace(&bytes[1..]); - let ptr = bytes.as_ptr(); - let rest = skip_expression(bytes); - if !added && ptr != rest.as_ptr() { - if result.ends_with('/') { - result.push_str("**"); - } else { - result.push_str("*/**"); - } - added = true; - } - bytes = skip_comments_and_whitespace(rest); - if bytes.is_empty() || matches!(bytes[0], b'"' | b'\'') { - break; - } - } - } else { - return None; - } - } - - if result.is_empty() { - None - } else { - Some(result) - } -} - -fn read_string(bytes: &[u8]) -> Option<(Cow<'_, str>, &[u8])> { - if bytes.is_empty() { - return None; - } - - let quote = bytes[0]; - if quote == b'`' { - return read_template_string(bytes); - } - - if quote != b'\'' && quote != b'"' { - return None; - } - - let mut i = 1; - while i < bytes.len() { - match bytes[i] { - b'\\' => { - i += 1; - if i + 1 < bytes.len() && bytes[i] == b'\r' && bytes[i + 1] == b'\n' { - i += 1; - } - } - b'\r' | b'\n' => break, - c if c == quote => { - return Some(( - escape_glob(unsafe { std::str::from_utf8_unchecked(&bytes[1..i]) }), - &bytes[i + 1..], - )) - } - _ => {} - } - - i += 1; - } - - None -} - -fn read_template_string(mut bytes: &[u8]) -> Option<(Cow<'_, str>, &[u8])> { - let mut i = 1; - let mut braces = 0; - let mut result = Cow::Borrowed(""); - let mut start = 1; - while i < bytes.len() { - match bytes[i] { - b'$' if braces == 0 && i + 1 < bytes.len() && bytes[i + 1] == b'{' => { - // Add current segment. - let s = unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) }; - if !s.is_empty() { - if result.ends_with("**") && !s.starts_with('/') { - result += "/*"; - } - result += escape_glob(unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) }); - } else if result.is_empty() { - return None; - } - - if result.ends_with('/') { - result += "**"; - } else { - result += "*/**"; - } - i += 1; - braces += 1; - } - b'}' if braces > 0 => { - braces -= 1; - if braces == 0 { - start = i + 1; - } - } - b'\\' if braces == 0 => { - i += 1; - } - b'`' if braces == 0 => { - // String end. Add last segment. - let s = escape_glob(unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) }); - if !s.is_empty() { - if result.ends_with("**") && !s.starts_with('/') { - result += "/*"; - } - if result.is_empty() { - return Some((s, &bytes[i + 1..])); - } - result += s; - } - return Some((result, &bytes[i + 1..])); - } - _ => {} - } - - i += 1; - if braces > 0 { - bytes = skip_comments_and_whitespace(&bytes[i..]); - i = 0; - } - } - - None -} - -fn escape_glob(s: &str) -> Cow<'_, str> { - let mut result = Cow::Borrowed(""); - let mut start = 0; - for (index, matched) in s.match_indices(&['*', '?', '[', ']', '{', '}', '(', ')', '!', '\\']) { - result += &s[start..index]; - result += "\\"; - result += matched; - start = index + 1; - } - - result += &s[start..]; - result -} - -fn skip_comments_and_whitespace(bytes: &[u8]) -> &[u8] { - let mut i = 0; - while i < bytes.len() { - let ch = bytes[i]; - if ch == b'/' { - if i + 1 < bytes.len() { - let next_ch = bytes[i + 1]; - i += 2; - if next_ch == b'/' { - while i < bytes.len() { - let ch = bytes[i]; - if matches!(ch, b'\n' | b'\r') { - break; - } - i += 1; - } - } else if next_ch == b'*' { - while i < bytes.len() { - let ch = bytes[i]; - if ch == b'*' && i + 1 < bytes.len() && bytes[i + 1] == b'/' { - i += 2; - break; - } - i += 1; - } - } - } - } else if !is_br_or_ws(ch) { - return &bytes[i..]; - } - i += 1; - } - - &bytes[i..] -} - -fn skip_expression(mut bytes: &[u8]) -> &[u8] { - let mut stack: [u8; 1024] = [0; 1024]; - let mut stack_len = 0; - while !bytes.is_empty() { - bytes = skip_comments_and_whitespace(bytes); - if !bytes.is_empty() { - let ch = bytes[0]; - - match ch { - b'{' | b'(' | b'[' => { - if stack_len >= stack.len() { - return bytes; - } - - stack[stack_len] = ch; - stack_len += 1; - } - ch @ (b'}' | b')' | b']') => { - let opposite = match ch { - b'}' => b'{', - b')' => b'(', - b']' => b'[', - _ => unreachable!(), - }; - if stack_len > 0 && opposite == stack[stack_len - 1] { - stack_len -= 1; - } else { - return bytes; - } - } - b'+' if stack_len == 0 && bytes.len() > 1 => return &bytes[1..], - b'\'' | b'"' if stack_len == 0 => return bytes, - _ => {} - } - } - bytes = &bytes[1..] - } - - bytes -} - -#[inline] -fn is_br_or_ws(c: u8) -> bool { - c > 8 && c < 14 || c == 32 || c == 160 -} - -pub fn resolve_path, B: AsRef>(base: A, subpath: B) -> PathBuf { - let subpath = subpath.as_ref(); - let mut components = subpath.components().peekable(); - if subpath.is_absolute() || matches!(components.peek(), Some(Component::Prefix(..))) { - return subpath.to_path_buf(); - } - - let mut ret = base.as_ref().to_path_buf(); - ret.pop(); - for component in subpath.components() { - match component { - Component::Prefix(..) | Component::RootDir => unreachable!(), - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - - ret -} - -pub fn build_esm_graph( - file: &Path, - project_root: &Path, - resolver_cache: &parcel_resolver_old::Cache, - cache: &Cache, -) -> Result { - let visitor = EsmGraphBuilder { - visited: DashSet::new(), - visited_globs: DashSet::new(), - invalidations: Invalidations::default(), - cjs_resolver: Resolver::node( - Cow::Borrowed(project_root), - CacheCow::Borrowed(resolver_cache), - ), - esm_resolver: Resolver::node_esm( - Cow::Borrowed(project_root), - CacheCow::Borrowed(resolver_cache), - ), - cache, - }; - - visitor.build(file)?; - Ok(visitor.invalidations) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_glob() { - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + name + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features' + name + '.js'"), - Some("caniuse-lite/data/features*/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + name + '/index.js'"), - Some("caniuse-lite/data/features/**/index.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + (a + b) + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + (a ? 'foo' : 'bar') + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + a + b + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + a + 'test' + '.js'"), - Some("caniuse-lite/data/features/**/*test.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + a + /* 'hello' */ + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + a + \n // 'hello'\n + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + foo('hi') + '.js'"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' + 'hi' + '.js'"), - Some("caniuse-lite/data/features/hi.js".into()) - ); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/[features]/' + name + '.js'"), - Some("caniuse-lite/data/\\[features\\]/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("`caniuse-lite/data/features/${name}.js`"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("`caniuse-lite/data/features${name}.js`"), - Some("caniuse-lite/data/features*/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("`caniuse-lite/data/[features]/${name}.js`"), - Some("caniuse-lite/data/\\[features\\]/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("`caniuse-lite/data/features/${/* } */ name}.js`"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!( - specifier_to_glob("`caniuse-lite/data/features/${\n// }\n name}.js`"), - Some("caniuse-lite/data/features/**/*.js".into()) - ); - assert_eq!(specifier_to_glob("file"), None); - assert_eq!(specifier_to_glob("file + '.js'"), None); - assert_eq!(specifier_to_glob("test"), None); - assert_eq!(specifier_to_glob("name + 'test'"), None); - assert_eq!(specifier_to_glob("`${name}/test`"), None); - assert_eq!( - specifier_to_glob("'caniuse-lite/data/features/' - '.js'"), - None - ); - } -} diff --git a/packages/utils/dev-dep-resolver-old/src/main.rs b/packages/utils/dev-dep-resolver-old/src/main.rs deleted file mode 100644 index e556db4ea0e..00000000000 --- a/packages/utils/dev-dep-resolver-old/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::borrow::Cow; -use std::sync::Arc; - -use parcel_dev_dep_resolver_old::build_esm_graph; -use parcel_resolver_old::Cache; -use parcel_resolver_old::CacheCow; -use parcel_resolver_old::OsFileSystem; -use parcel_resolver_old::Resolution; -use parcel_resolver_old::Resolver; -use parcel_resolver_old::SpecifierType; - -fn main() { - let contents = std::fs::read_to_string("package.json").unwrap(); - let pkg: serde_json::Value = serde_json::from_str(&contents).unwrap(); - let deps = pkg.get("dependencies").unwrap().as_object().unwrap(); - let cwd = std::env::current_dir().unwrap(); - - let cache = Cache::new(Arc::new(OsFileSystem)); - let cjs_resolver = Resolver::node(Cow::Borrowed(&cwd), CacheCow::Borrowed(&cache)); - let esm_graph_cache = parcel_dev_dep_resolver_old::Cache::default(); - - deps.keys().for_each(|dep| { - #[cfg(debug_assertions)] - println!("------------ {} -----------", dep); - let resolved = match cjs_resolver.resolve(dep, &cwd, SpecifierType::Esm).result { - Ok(res) => res.0, - Err(e) => { - #[cfg(debug_assertions)] - println!("FAILED TO RESOLVE {} {:?}", dep, e); - return; - } - }; - - if let Resolution::Path(p) = resolved { - match build_esm_graph(&p, &cwd, &cache, &esm_graph_cache) { - Ok(_res) => { - // #[cfg(debug_assertions)] - // println!("{:?}", res) - } - Err(err) => { - #[cfg(debug_assertions)] - println!("FAIL: {:?}", err) - } - } - } - - #[cfg(debug_assertions)] - println!(); - }); -} diff --git a/packages/utils/dev-dep-resolver/src/lib.rs b/packages/utils/dev-dep-resolver/src/lib.rs index 9963b7158b1..9b2795d7896 100644 --- a/packages/utils/dev-dep-resolver/src/lib.rs +++ b/packages/utils/dev-dep-resolver/src/lib.rs @@ -1,22 +1,14 @@ -use std::borrow::Cow; -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -use dashmap::DashMap; -use dashmap::DashSet; -use es_module_lexer::lex; -use es_module_lexer::ImportKind; -use parcel_resolver::CacheCow; -use parcel_resolver::Invalidations; -use parcel_resolver::ModuleType; -use parcel_resolver::Resolution; -use parcel_resolver::ResolveOptions; -use parcel_resolver::Resolver; -use parcel_resolver::ResolverError; -use parcel_resolver::Specifier; -use parcel_resolver::SpecifierError; -use parcel_resolver::SpecifierType; +use std::{ + borrow::Cow, + path::{Component, Path, PathBuf}, +}; + +use dashmap::{DashMap, DashSet}; +use es_module_lexer::{lex, ImportKind}; +use parcel_resolver::{ + CacheCow, Invalidations, ModuleType, Resolution, ResolveOptions, Resolver, ResolverError, + Specifier, SpecifierError, SpecifierType, +}; // use rayon::prelude::{ParallelBridge, ParallelIterator}; #[derive(Debug)] @@ -97,12 +89,7 @@ impl<'a> EsmGraphBuilder<'a> { if let Some(invalidations) = self.cache.entries.get(file) { self.invalidations.extend(&invalidations); - for p in invalidations - .invalidate_on_file_change - .read() - .unwrap() - .iter() - { + for p in invalidations.invalidate_on_file_change.iter() { self.build(&p)?; } return Ok(()); @@ -185,7 +172,7 @@ impl<'a> EsmGraphBuilder<'a> { let specifier = Specifier::parse(pattern, SpecifierType::Esm, resolver.flags)?; let pattern = match specifier { (Specifier::Absolute(path), _) => path, - (Specifier::Relative(relative), _) => resolve_path(from, relative), + (Specifier::Relative(relative), _) => Cow::Owned(resolve_path(from, relative)), (Specifier::Package(mut package, subpath), _) => { // Resolve the package.json file within the package rather than the package entry. // TODO: how should we handle package exports? @@ -197,7 +184,7 @@ impl<'a> EsmGraphBuilder<'a> { invalidations, ResolveOptions::default(), ) { - Ok((Resolution::Path(p), _)) => p.parent().unwrap().join(&subpath), + Ok((Resolution::Path(p), _)) => Cow::Owned(p.parent().unwrap().join(subpath.as_ref())), _ => return Ok(()), } } @@ -207,7 +194,7 @@ impl<'a> EsmGraphBuilder<'a> { // Invalidate when new files match the glob. invalidations.invalidate_on_glob_create(pattern.to_string_lossy()); - if self.visited_globs.contains(&pattern) { + if self.visited_globs.contains(pattern.as_ref()) { return Ok(()); } diff --git a/packages/utils/dev-dep-resolver/src/main.rs b/packages/utils/dev-dep-resolver/src/main.rs index f104e08a6e0..355d207a9a6 100644 --- a/packages/utils/dev-dep-resolver/src/main.rs +++ b/packages/utils/dev-dep-resolver/src/main.rs @@ -1,13 +1,7 @@ -use std::borrow::Cow; -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use parcel_dev_dep_resolver::build_esm_graph; -use parcel_resolver::Cache; -use parcel_resolver::CacheCow; -use parcel_resolver::OsFileSystem; -use parcel_resolver::Resolution; -use parcel_resolver::Resolver; -use parcel_resolver::SpecifierType; +use parcel_resolver::{Cache, CacheCow, OsFileSystem, Resolution, Resolver, SpecifierType}; fn main() { let contents = std::fs::read_to_string("package.json").unwrap(); diff --git a/packages/utils/node-resolver-core/package.json b/packages/utils/node-resolver-core/package.json index 141ab52b389..11ac4ebef3f 100644 --- a/packages/utils/node-resolver-core/package.json +++ b/packages/utils/node-resolver-core/package.json @@ -27,7 +27,6 @@ "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", "@parcel/diagnostic": "2.12.0", - "@parcel/feature-flags": "2.12.0", "@parcel/fs": "2.12.0", "@parcel/rust": "2.12.0", "@parcel/utils": "2.12.0", diff --git a/packages/utils/node-resolver-core/src/Wrapper.js b/packages/utils/node-resolver-core/src/Wrapper.js index 85474d246b6..f9f81067d24 100644 --- a/packages/utils/node-resolver-core/src/Wrapper.js +++ b/packages/utils/node-resolver-core/src/Wrapper.js @@ -11,10 +11,9 @@ import type { } from '@parcel/types'; import type {FileSystem} from '@parcel/fs'; import type {PackageManager} from '@parcel/package-manager'; -import {getFeatureFlag} from '@parcel/feature-flags'; import type {Diagnostic} from '@parcel/diagnostic'; import {NodeFS} from '@parcel/fs'; -import {init, Resolver as ResolverNew, ResolverOld} from '@parcel/rust'; +import {init, Resolver} from '@parcel/rust'; import builtins, {empty} from './builtins'; import path from 'path'; import { @@ -83,9 +82,6 @@ export default class NodeResolver { let resolver = this.resolversByEnv.get(options.env.id); if (!resolver) { await init?.(); - const Resolver = getFeatureFlag('ownedResolverStructures') - ? ResolverNew - : ResolverOld; resolver = new Resolver(this.options.projectRoot, { fs: this.options.fs instanceof NodeFS && @@ -493,13 +489,14 @@ export default class NodeResolver { }; } case 'JsonError': { + let pkgContent = await this.options.fs.readFile(error.path, 'utf8'); return { message: 'Error parsing JSON', codeFrames: [ { - filePath: error.file.path, + filePath: error.path, language: 'json', - code: error.file.contents, + code: pkgContent, // TODO codeHighlights: [ { diff --git a/packages/utils/node-resolver-core/src/index.js b/packages/utils/node-resolver-core/src/index.js index 88322128d96..656e444012a 100644 --- a/packages/utils/node-resolver-core/src/index.js +++ b/packages/utils/node-resolver-core/src/index.js @@ -1,14 +1,3 @@ // @flow - -import {getFeatureFlag} from '@parcel/feature-flags'; -import {Resolver as ResolverNew, ResolverOld} from '@parcel/rust'; - -export const ResolverBase: typeof ResolverNew = getFeatureFlag( - 'ownedResolverStructures', -) - ? ResolverNew - : // $FlowFixMe unfortunately this can't be typed properly. This may be an issue if something does instanceof checks against a direct reference to @parcel/rust, but will be fine otherwise. - ResolverOld; - export {default} from './Wrapper'; -export {init} from '@parcel/rust'; +export {Resolver as ResolverBase, init} from '@parcel/rust'; diff --git a/packages/utils/node-resolver-core/test/resolver.js b/packages/utils/node-resolver-core/test/resolver.js index db3f4a712b8..84cefc3fd75 100644 --- a/packages/utils/node-resolver-core/test/resolver.js +++ b/packages/utils/node-resolver-core/test/resolver.js @@ -8,7 +8,6 @@ import {loadConfig as configCache} from '@parcel/utils'; import {createEnvironment} from '@parcel/core/src/Environment'; import Environment from '@parcel/core/src/public/Environment'; import {DEFAULT_OPTIONS} from '@parcel/core/test/test-utils'; -import {setFeatureFlags, DEFAULT_FEATURE_FLAGS} from '@parcel/feature-flags'; const rootDir = path.join(__dirname, 'fixture'); @@ -36,513 +35,879 @@ const BROWSER_ENV = new Environment( DEFAULT_OPTIONS, ); -[true, false].forEach(value => { - beforeEach(() => { - setFeatureFlags({ - ...DEFAULT_FEATURE_FLAGS, - ownedResolverStructures: value, +describe('resolver', function () { + let resolver, prodResolver; + + beforeEach(async function () { + await overlayFS.mkdirp(rootDir); + await ncp(rootDir, rootDir); + + // Create the symlinks here to prevent cross platform and git issues + await outputFS.symlink( + path.join(rootDir, 'packages/source'), + path.join(rootDir, 'node_modules/source'), + ); + await outputFS.symlink( + path.join( + rootDir, + 'node_modules/.pnpm/source-pnpm@1.0.0/node_modules/source-pnpm', + ), + path.join(rootDir, 'node_modules/source-pnpm'), + ); + await outputFS.symlink( + path.join(rootDir, 'packages/source-alias'), + path.join(rootDir, 'node_modules/source-alias'), + ); + await outputFS.symlink( + path.join(rootDir, 'packages/source-alias-glob'), + path.join(rootDir, 'node_modules/source-alias-glob'), + ); + await outputFS.symlink( + path.join(rootDir, 'packages/source-exports'), + path.join(rootDir, 'node_modules/source-exports'), + ); + await outputFS.symlink( + path.join(rootDir, 'bar.js'), + path.join(rootDir, 'baz.js'), + ); + await outputFS.symlink( + path.join(rootDir, 'nested'), + path.join(rootDir, 'symlinked-nested'), + ); + + resolver = new NodeResolver({ + fs: overlayFS, + projectRoot: rootDir, + mode: 'development', + packageExports: true, }); - }); - afterEach(() => { - setFeatureFlags({ - ...DEFAULT_FEATURE_FLAGS, - }); - }); - describe(`resolver with ff ${String(value)}`, function () { - let resolver, prodResolver; + prodResolver = new NodeResolver({ + fs: overlayFS, + projectRoot: rootDir, + mode: 'production', + packageExports: true, + }); - beforeEach(async function () { - await overlayFS.mkdirp(rootDir); - await ncp(rootDir, rootDir); + configCache.clear(); + }); - // Create the symlinks here to prevent cross platform and git issues - await outputFS.symlink( - path.join(rootDir, 'packages/source'), - path.join(rootDir, 'node_modules/source'), - ); - await outputFS.symlink( - path.join( - rootDir, - 'node_modules/.pnpm/source-pnpm@1.0.0/node_modules/source-pnpm', - ), - path.join(rootDir, 'node_modules/source-pnpm'), - ); - await outputFS.symlink( - path.join(rootDir, 'packages/source-alias'), - path.join(rootDir, 'node_modules/source-alias'), - ); - await outputFS.symlink( - path.join(rootDir, 'packages/source-alias-glob'), - path.join(rootDir, 'node_modules/source-alias-glob'), - ); - await outputFS.symlink( - path.join(rootDir, 'packages/source-exports'), - path.join(rootDir, 'node_modules/source-exports'), - ); - await outputFS.symlink( - path.join(rootDir, 'bar.js'), - path.join(rootDir, 'baz.js'), - ); - await outputFS.symlink( - path.join(rootDir, 'nested'), - path.join(rootDir, 'symlinked-nested'), - ); + function normalize(res) { + return { + filePath: res?.filePath, + invalidateOnFileCreate: + res?.invalidateOnFileCreate?.sort((a, b) => { + let ax = + a.filePath ?? + a.glob ?? + (a.aboveFilePath != null && a.fileName != null + ? a.aboveFilePath + a.fileName + : ''); + let bx = + b.filePath ?? + b.glob ?? + (b.aboveFilePath != null && b.fileName != null + ? b.aboveFilePath + b.fileName + : ''); + return ax < bx ? -1 : 1; + }) ?? [], + invalidateOnFileChange: res?.invalidateOnFileChange?.sort() ?? [], + sideEffects: res?.sideEffects ?? true, + }; + } + + function check(resolved, expected) { + assert.deepEqual(normalize(resolved), normalize(expected)); + } + + describe('file paths', function () { + it('should resolve a relative path with an extension', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './bar.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - resolver = new NodeResolver({ - fs: overlayFS, - projectRoot: rootDir, - mode: 'development', - packageExports: true, + it('should resolve a relative path without an extension', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - prodResolver = new NodeResolver({ - fs: overlayFS, - projectRoot: rootDir, - mode: 'production', - packageExports: true, - }); - - configCache.clear(); - }); - - function normalize(res) { - return { - filePath: res?.filePath, - invalidateOnFileCreate: - res?.invalidateOnFileCreate?.sort((a, b) => { - let ax = - a.filePath ?? - a.glob ?? - (a.aboveFilePath != null && a.fileName != null - ? a.aboveFilePath + a.fileName - : ''); - let bx = - b.filePath ?? - b.glob ?? - (b.aboveFilePath != null && b.fileName != null - ? b.aboveFilePath + b.fileName - : ''); - return ax < bx ? -1 : 1; - }) ?? [], - invalidateOnFileChange: res?.invalidateOnFileChange?.sort() ?? [], - sideEffects: res?.sideEffects ?? true, - }; - } - - function check(resolved, expected) { - assert.deepEqual(normalize(resolved), normalize(expected)); - } - - describe('file paths', function () { - it('should resolve a relative path with an extension', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './bar.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve an absolute path from the root module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'nested', 'test.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - it('should resolve a relative path without an extension', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve an absolute path from a node_modules folder', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'foo', 'index.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - it('should resolve an absolute path from the root module', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'nested', 'test.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve a tilde path from the root module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '~/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'nested', 'test.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - it('should resolve an absolute path from a node_modules folder', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'foo', 'index.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve a tilde path from the root module without a slash', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '~bar', + specifierType: 'esm', + parent: path.join(rootDir, 'nested', 'test.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); + }); - it('should resolve a tilde path from the root module', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '~/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'nested', 'test.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve a tilde path from a node_modules folder', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '~/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'foo', 'nested', 'baz.js'), }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'node_modules', 'foo', 'bar.js'), + ); + }); - it('should resolve a tilde path from the root module without a slash', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '~bar', - specifierType: 'esm', - parent: path.join(rootDir, 'nested', 'test.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve an index file in a directory', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './nested', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'nested', 'index.js'), + ); + }); - it('should resolve a tilde path from a node_modules folder', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '~/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'foo', 'nested', 'baz.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'node_modules', 'foo', 'bar.js'), - ); + it('should not resolve an index file in a directory for URL specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './nested', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + {message: "Cannot load file './nested' in './'.", hints: []}, + ]); + }); - it('should resolve an index file in a directory', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './nested', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'nested', 'index.js'), - ); - }); + it('should resolve a file with a question mark with CommonJS specifiers', async function () { + // Windows filenames cannot contain question marks. + if (process.platform === 'win32') { + return; + } - it('should not resolve an index file in a directory for URL specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './nested', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ - {message: "Cannot load file './nested' in './'.", hints: []}, - ]); + await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './a?b.js', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'a?b.js')); + }); - it('should resolve a file with a question mark with CommonJS specifiers', async function () { - // Windows filenames cannot contain question marks. - if (process.platform === 'win32') { - return; - } + it('should not resolve a file with a question mark with ESM specifiers', async function () { + // Windows filenames cannot contain question marks. + if (process.platform === 'win32') { + return; + } - await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './a?b.js', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'a?b.js'), - ); + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './a?b.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + {message: "Cannot load file './a' in './'.", hints: []}, + ]); + }); - it('should not resolve a file with a question mark with ESM specifiers', async function () { - // Windows filenames cannot contain question marks. - if (process.platform === 'win32') { - return; - } + it('should resolve a file with an encoded question mark with ESM specifiers', async function () { + // Windows filenames cannot contain question marks. + if (process.platform === 'win32') { + return; + } - await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './a?b.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ - {message: "Cannot load file './a' in './'.", hints: []}, - ]); + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './a%3Fb.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'a?b.js')); + }); - it('should resolve a file with an encoded question mark with ESM specifiers', async function () { - // Windows filenames cannot contain question marks. - if (process.platform === 'win32') { - return; - } - - await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + it('should not support percent encoding in CommonJS specifiers', async function () { + // Windows filenames cannot contain question marks. + if (process.platform === 'win32') { + return; + } + + await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './a%3Fb.js', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + { + message: "Cannot load file './a%3Fb.js' in './'.", + hints: ["Did you mean '__./a?b.js__'?"], + }, + ]); + }); - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './a%3Fb.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'a?b.js'), - ); + it('should support query params for ESM specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './nested?foo=bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'nested', 'index.js'), + ); + // assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=bar'); + }); - it('should not support percent encoding in CommonJS specifiers', async function () { - // Windows filenames cannot contain question marks. - if (process.platform === 'win32') { - return; - } - - await overlayFS.writeFile(path.join(rootDir, 'a?b.js'), ''); + it('should not support query params for CommonJS specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './nested?foo=bar', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + {message: "Cannot load file './nested?foo=bar' in './'.", hints: []}, + ]); + }); + }); - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './a%3Fb.js', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ + describe('builtins', function () { + it('should resolve node builtin modules', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'zlib', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: require.resolve('browserify-zlib'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/browserify-zlib', + aboveFilePath: rootDir, + }, { - message: "Cannot load file './a%3Fb.js' in './'.", - hints: ["Did you mean '__./a?b.js__'?"], + fileName: 'package.json', + aboveFilePath: path.dirname(require.resolve('browserify-zlib/lib')), }, - ]); + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + require.resolve('browserify-zlib/package.json'), + ], }); + }); - it('should support query params for ESM specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './nested?foo=bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'nested', 'index.js'), - ); - // assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=bar'); + it('Should be able to handle node: prefixes', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'node:zlib', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: require.resolve('browserify-zlib'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/browserify-zlib', + aboveFilePath: rootDir, + }, + { + fileName: 'package.json', + aboveFilePath: path.dirname(require.resolve('browserify-zlib/lib')), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + require.resolve('browserify-zlib/package.json'), + ], }); + }); - it('should not support query params for CommonJS specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './nested?foo=bar', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ - {message: "Cannot load file './nested?foo=bar' in './'.", hints: []}, - ]); + it('should resolve unimplemented node builtin modules to an empty file', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'fs', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(__dirname, '..', 'src', '_empty.js'), + sideEffects: undefined, + query: undefined, }); }); - describe('builtins', function () { - it('should resolve node builtin modules', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'zlib', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: require.resolve('browserify-zlib'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/browserify-zlib', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.dirname( - require.resolve('browserify-zlib/lib'), - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - require.resolve('browserify-zlib/package.json'), - ], - }); + it('should exclude node builtin modules with --target=node', async function () { + let resolved = await resolver.resolve({ + env: NODE_ENV, + filename: 'zlib', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + check(resolved, {isExcluded: true}); + }); - it('Should be able to handle node: prefixes', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'node:zlib', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: require.resolve('browserify-zlib'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/browserify-zlib', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.dirname( - require.resolve('browserify-zlib/lib'), - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - require.resolve('browserify-zlib/package.json'), - ], - }); + it('should exclude the electron module in electron environments', async function () { + let resolved = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'electron-main', + isLibrary: true, + includeNodeModules: true, + }), + DEFAULT_OPTIONS, + ), + filename: 'electron', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + sourcePath: path.join(rootDir, 'foo.js'), }); - it('should resolve unimplemented node builtin modules to an empty file', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'fs', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(__dirname, '..', 'src', '_empty.js'), - sideEffects: undefined, - query: undefined, - }); + check(resolved, {isExcluded: true}); + }); + }); + + describe('node_modules', function () { + it('should resolve a node_modules index.js', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/foo', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'foo', 'package.json'), + ], }); + }); - it('should exclude node builtin modules with --target=node', async function () { - let resolved = await resolver.resolve({ - env: NODE_ENV, - filename: 'zlib', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, {isExcluded: true}); + it('should resolve a node_modules package.main', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-main', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'package-main', 'main.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-main', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'package-main', 'package.json'), + ], }); + }); - it('should exclude the electron module in electron environments', async function () { - let resolved = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'electron-main', - isLibrary: true, - includeNodeModules: true, - }), - DEFAULT_OPTIONS, - ), - filename: 'electron', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - sourcePath: path.join(rootDir, 'foo.js'), - }); + it('should resolve a node_modules package.module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-module', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-module', + 'module.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-module', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'package-module', 'package.json'), + ], + }); + }); - check(resolved, {isExcluded: true}); + it('should resolve a node_modules package.browser main field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-browser', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser', + 'browser.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-browser', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'package-browser', 'package.json'), + ], }); }); - describe('node_modules', function () { - it('should resolve a node_modules index.js', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/foo', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'foo', 'package.json'), - ], - }); + it('should not resolve a node_modules package.browser main field with --target=node', async function () { + let resolved = await resolver.resolve({ + env: NODE_INCLUDE_ENV, + filename: 'package-browser', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser', + 'main.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-browser', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'package-browser', 'package.json'), + ], }); + }); - it('should resolve a node_modules package.main', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-main', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'package-main', - 'main.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/package-main', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'package-main', 'package.json'), - ], - }); + it('should fall back to index.js when it cannot find package.main', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-fallback', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'index.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-fallback', + aboveFilePath: rootDir, + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.cjs', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.mjs', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.jsx', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.ts', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.tsx', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.js', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js.json', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'main.js/package.json', + ), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'package-fallback', + 'package.json', + ), + ], + }); + }); + + it('should resolve a node_module package.main pointing to a directory', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-main-directory', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested', + 'index.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-main-directory', + aboveFilePath: rootDir, + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested', + 'package.json', + ), + }, + { + fileName: 'package.json', + aboveFilePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.js', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.json', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.jsx', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.cjs', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.mjs', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.ts', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'nested.tsx', + ), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'package-main-directory', + 'package.json', + ), + ], + }); + }); + + it('should resolve a file inside a node_modules folder', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'foo/nested/baz', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'foo', 'nested', 'baz.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/foo', + aboveFilePath: rootDir, + }, + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'node_modules', 'foo', 'nested'), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'foo', 'package.json'), + ], + }); + }); + + it('should resolve a scoped module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '@scope/pkg', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/@scope/pkg', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', '@scope', 'pkg', 'package.json'), + ], + }); + }); + + it('should resolve a file inside a scoped module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '@scope/pkg/foo/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.resolve(rootDir, 'node_modules/@scope/pkg/foo/bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/@scope/pkg', + aboveFilePath: rootDir, + }, + { + fileName: 'package.json', + aboveFilePath: path.join( + rootDir, + 'node_modules', + '@scope', + 'pkg', + 'foo', + ), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', '@scope', 'pkg', 'package.json'), + ], }); + }); - it('should resolve a node_modules package.module', async function () { + describe('sideEffects: false', function () { + it('should determine sideEffects correctly (file)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-module', + filename: 'side-effects-false/src/index.js', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join( + filePath: path.resolve( rootDir, - 'node_modules', - 'package-module', - 'module.js', + 'node_modules/side-effects-false/src/index.js', ), - sideEffects: undefined, + sideEffects: false, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-module', + fileName: 'node_modules/side-effects-false', aboveFilePath: rootDir, }, + { + fileName: 'package.json', + aboveFilePath: path.join( + rootDir, + 'node_modules', + 'side-effects-false', + 'src', + ), + }, ], invalidateOnFileChange: [ path.join(rootDir, 'package.json'), @@ -550,34 +915,41 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-module', + 'side-effects-false', 'package.json', ), ], }); }); - it('should resolve a node_modules package.browser main field', async function () { + it('should determine sideEffects correctly (extensionless file)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-browser', + filename: 'side-effects-false/src/index', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join( + filePath: path.resolve( rootDir, - 'node_modules', - 'package-browser', - 'browser.js', + 'node_modules/side-effects-false/src/index.js', ), - sideEffects: undefined, + sideEffects: false, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-browser', + fileName: 'node_modules/side-effects-false', aboveFilePath: rootDir, }, + { + fileName: 'package.json', + aboveFilePath: path.join( + rootDir, + 'node_modules', + 'side-effects-false', + 'src', + ), + }, ], invalidateOnFileChange: [ path.join(rootDir, 'package.json'), @@ -585,34 +957,50 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-browser', + 'side-effects-false', 'package.json', ), ], }); }); - it('should not resolve a node_modules package.browser main field with --target=node', async function () { + it('should determine sideEffects correctly (sub folder)', async function () { let resolved = await resolver.resolve({ - env: NODE_INCLUDE_ENV, - filename: 'package-browser', + env: BROWSER_ENV, + filename: 'side-effects-false/src/', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join( + filePath: path.resolve( rootDir, - 'node_modules', - 'package-browser', - 'main.js', + 'node_modules/side-effects-false/src/index.js', ), - sideEffects: undefined, + sideEffects: false, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-browser', + fileName: 'node_modules/side-effects-false', aboveFilePath: rootDir, }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'side-effects-false', + 'src', + 'package.json', + ), + }, + { + aboveFilePath: path.join( + rootDir, + 'node_modules', + 'side-effects-false', + 'src', + ), + fileName: 'package.json', + }, ], invalidateOnFileChange: [ path.join(rootDir, 'package.json'), @@ -620,104 +1008,153 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-browser', + 'side-effects-false', 'package.json', ), ], }); }); - it('should fall back to index.js when it cannot find package.main', async function () { + it('should determine sideEffects correctly (main field)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-fallback', + filename: 'side-effects-false/src/', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join( + filePath: path.resolve( rootDir, - 'node_modules', - 'package-fallback', - 'index.js', + 'node_modules/side-effects-false/src/index.js', ), - sideEffects: undefined, + sideEffects: false, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-fallback', + fileName: 'node_modules/side-effects-false', aboveFilePath: rootDir, }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'package-fallback', - 'main.js.cjs', + 'side-effects-false', + 'src', + 'package.json', ), }, { - filePath: path.join( + aboveFilePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js.mjs', + 'side-effects-false', + 'src', ), + fileName: 'package.json', }, - { - filePath: path.join( - rootDir, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'side-effects-false', + 'package.json', + ), + ], + }); + }); + + it('should determine sideEffects correctly (main field exists in upward package)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'side-effects-package-redirect-up/foo/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.resolve( + rootDir, + 'node_modules/side-effects-package-redirect-up/foo/real-bar.js', + ), + sideEffects: false, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/side-effects-package-redirect-up', + aboveFilePath: rootDir, + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-up', + 'foo', + 'bar', + ), + }, + { + filePath: path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-up', + 'foo', + 'bar.js', + ), + }, + { + filePath: path.join( + rootDir, 'node_modules', - 'package-fallback', - 'main.js.jsx', + 'side-effects-package-redirect-up', + 'foo', + 'bar.json', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js.ts', + 'side-effects-package-redirect-up', + 'foo', + 'bar.jsx', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js.tsx', + 'side-effects-package-redirect-up', + 'foo', + 'bar.cjs', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js.js', + 'side-effects-package-redirect-up', + 'foo', + 'bar.mjs', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js.json', + 'side-effects-package-redirect-up', + 'foo', + 'bar.ts', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-fallback', - 'main.js/package.json', + 'side-effects-package-redirect-up', + 'foo', + 'bar.tsx', ), }, ], @@ -727,115 +1164,117 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-fallback', + 'side-effects-package-redirect-up', + 'package.json', + ), + path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-up', + 'foo', + 'bar', + 'package.json', + ), + path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-up', + 'foo', 'package.json', ), ], }); }); - it('should resolve a node_module package.main pointing to a directory', async function () { + it('should determine sideEffects correctly (main field exists in downward package)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-main-directory', + filename: 'side-effects-package-redirect-down/foo/bar', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join( + filePath: path.resolve( rootDir, - 'node_modules', - 'package-main-directory', - 'nested', - 'index.js', + 'node_modules/side-effects-package-redirect-down/foo/bar/baz/real-bar.js', ), - sideEffects: undefined, + sideEffects: false, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-main-directory', + fileName: 'node_modules/side-effects-package-redirect-down', aboveFilePath: rootDir, }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested', - 'package.json', - ), - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'package-main-directory', - 'nested', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'package-main-directory', - 'nested', + 'side-effects-package-redirect-down', + 'foo', + 'bar', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.js', + 'side-effects-package-redirect-down', + 'foo', + 'bar.js', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.json', + 'side-effects-package-redirect-down', + 'foo', + 'bar.jsx', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.jsx', + 'side-effects-package-redirect-down', + 'foo', + 'bar.json', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.cjs', + 'side-effects-package-redirect-down', + 'foo', + 'bar.ts', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.mjs', + 'side-effects-package-redirect-down', + 'foo', + 'bar.tsx', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.ts', + 'side-effects-package-redirect-down', + 'foo', + 'bar.cjs', ), }, { filePath: path.join( rootDir, 'node_modules', - 'package-main-directory', - 'nested.tsx', + 'side-effects-package-redirect-down', + 'foo', + 'bar.mjs', ), }, ], @@ -845,1362 +1284,984 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-main-directory', + 'side-effects-package-redirect-down', + 'package.json', + ), + path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-down', + 'foo', + 'bar', + 'package.json', + ), + path.join( + rootDir, + 'node_modules', + 'side-effects-package-redirect-down', + 'foo', + 'bar', + 'baz', 'package.json', ), ], }); }); + }); - it('should resolve a file inside a node_modules folder', async function () { + describe('sideEffects: globs', function () { + it('should determine sideEffects correctly (matched)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'foo/nested/baz', + filename: 'side-effects-false-glob/a/index', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'foo', - 'nested', - 'baz.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/foo', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'foo', - 'nested', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'foo', 'package.json'), - ], - }); + check( + {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, + { + filePath: path.resolve( + rootDir, + 'node_modules/side-effects-false-glob/a/index.js', + ), + sideEffects: undefined, + }, + ); }); - - it('should resolve a scoped module', async function () { + it('should determine sideEffects correctly (unmatched)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: '@scope/pkg', + filename: 'side-effects-false-glob/b/index.js', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); - check(resolved, { - filePath: path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/@scope/pkg', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', '@scope', 'pkg', 'package.json'), - ], - }); + check( + {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, + { + filePath: path.resolve( + rootDir, + 'node_modules/side-effects-false-glob/b/index.js', + ), + sideEffects: false, + }, + ); }); - - it('should resolve a file inside a scoped module', async function () { + it('should determine sideEffects correctly (matched dotslash)', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: '@scope/pkg/foo/bar', + filename: 'side-effects-false-glob/sub/index.js', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); - check(resolved, { - filePath: path.resolve(rootDir, 'node_modules/@scope/pkg/foo/bar.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/@scope/pkg', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - '@scope', - 'pkg', - 'foo', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', '@scope', 'pkg', 'package.json'), - ], - }); - }); - - describe('sideEffects: false', function () { - it('should determine sideEffects correctly (file)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false/src/index.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { + check( + {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, + { filePath: path.resolve( rootDir, - 'node_modules/side-effects-false/src/index.js', + 'node_modules/side-effects-false-glob/sub/index.js', ), - sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-false', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'package.json', - ), - ], - }); + sideEffects: undefined, + }, + ); + }); + it('should determine sideEffects correctly (unmatched, prefix in subdir)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'side-effects-false-glob/sub/a/index.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - - it('should determine sideEffects correctly (extensionless file)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false/src/index', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { + check( + {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, + { filePath: path.resolve( rootDir, - 'node_modules/side-effects-false/src/index.js', + 'node_modules/side-effects-false-glob/sub/a/index.js', ), sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-false', - aboveFilePath: rootDir, - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'package.json', - ), - ], - }); + }, + ); + }); + it('should determine sideEffects correctly (only name)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'side-effects-false-glob/sub/index.json', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - - it('should determine sideEffects correctly (sub folder)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false/src/', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { + check( + {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, + { filePath: path.resolve( rootDir, - 'node_modules/side-effects-false/src/index.js', + 'node_modules/side-effects-false-glob/sub/index.json', ), - sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-false', - aboveFilePath: rootDir, - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - 'package.json', - ), - }, - { - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - ), - fileName: 'package.json', - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'package.json', - ), - ], - }); - }); + sideEffects: undefined, + }, + ); + }); + }); - it('should determine sideEffects correctly (main field)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false/src/', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false/src/index.js', - ), - sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-false', - aboveFilePath: rootDir, - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - 'package.json', - ), - }, - { - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'src', - ), - fileName: 'package.json', - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-false', - 'package.json', - ), - ], - }); - }); + it('should not resolve a node module for URL dependencies', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '@scope/pkg', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + {message: "Cannot load file './@scope/pkg' in './'.", hints: []}, + ]); + }); - it('should determine sideEffects correctly (main field exists in upward package)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-package-redirect-up/foo/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-package-redirect-up/foo/real-bar.js', - ), - sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-package-redirect-up', - aboveFilePath: rootDir, - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.js', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.json', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.jsx', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.cjs', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.mjs', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.ts', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar.tsx', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'package.json', - ), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'bar', - 'package.json', - ), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-up', - 'foo', - 'package.json', - ), - ], - }); - }); - - it('should determine sideEffects correctly (main field exists in downward package)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-package-redirect-down/foo/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-package-redirect-down/foo/bar/baz/real-bar.js', - ), - sideEffects: false, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/side-effects-package-redirect-down', - aboveFilePath: rootDir, - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.js', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.jsx', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.json', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.ts', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.tsx', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.cjs', - ), - }, - { - filePath: path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar.mjs', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'package.json', - ), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar', - 'package.json', - ), - path.join( - rootDir, - 'node_modules', - 'side-effects-package-redirect-down', - 'foo', - 'bar', - 'baz', - 'package.json', - ), - ], - }); - }); + it('should resolve a node module for URL dependencies with the npm: prefix', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'npm:@scope/pkg', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + assert.deepEqual( + nullthrows(resolved).filePath, + path.join(rootDir, 'node_modules', '@scope', 'pkg', 'index.js'), + ); + }); - describe('sideEffects: globs', function () { - it('should determine sideEffects correctly (matched)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false-glob/a/index', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check( - {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, - { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false-glob/a/index.js', - ), - sideEffects: undefined, - }, - ); - }); - it('should determine sideEffects correctly (unmatched)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false-glob/b/index.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check( - {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, - { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false-glob/b/index.js', - ), - sideEffects: false, - }, - ); - }); - it('should determine sideEffects correctly (matched dotslash)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false-glob/sub/index.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check( - {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, - { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false-glob/sub/index.js', - ), - sideEffects: undefined, - }, - ); - }); - it('should determine sideEffects correctly (unmatched, prefix in subdir)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false-glob/sub/a/index.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check( - {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, - { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false-glob/sub/a/index.js', - ), - sideEffects: false, - }, - ); - }); - it('should determine sideEffects correctly (only name)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'side-effects-false-glob/sub/index.json', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check( - {filePath: resolved?.filePath, sideEffects: resolved?.sideEffects}, - { - filePath: path.resolve( - rootDir, - 'node_modules/side-effects-false-glob/sub/index.json', - ), - sideEffects: undefined, - }, - ); - }); + it('should support query params for bare ESM specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '@scope/pkg?foo=2', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + nullthrows(resolved).filePath, + path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), + ); + assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=2'); + }); - it('should not resolve a node module for URL dependencies', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '@scope/pkg', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ - {message: "Cannot load file './@scope/pkg' in './'.", hints: []}, - ]); - }); + it('should not support query params for bare CommonJS specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '@scope/pkg?foo=2', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(nullthrows(resolved).diagnostics, [ + { + message: `Cannot find module '@scope/pkg?foo=2'`, + hints: ["Did you mean '__@scope/pkg__'?"], + }, + ]); + }); - it('should resolve a node module for URL dependencies with the npm: prefix', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'npm:@scope/pkg', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual( - nullthrows(resolved).filePath, - path.join(rootDir, 'node_modules', '@scope', 'pkg', 'index.js'), - ); + it('should support query params for npm: specifiers', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'npm:@scope/pkg?foo=2', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + nullthrows(resolved).filePath, + path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), + ); + assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=2'); + }); + }); - it('should support query params for bare ESM specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '@scope/pkg?foo=2', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), - ); - assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=2'); + describe('aliases', function () { + it('should alias the main file using the package.browser field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-browser-alias', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - - it('should not support query params for bare CommonJS specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '@scope/pkg?foo=2', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(nullthrows(resolved).diagnostics, [ + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'browser.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ { - message: `Cannot find module '@scope/pkg?foo=2'`, - hints: ["Did you mean '__@scope/pkg__'?"], + fileName: 'node_modules/package-browser-alias', + aboveFilePath: rootDir, }, - ]); - }); - - it('should support query params for npm: specifiers', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'npm:@scope/pkg?foo=2', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.resolve(rootDir, 'node_modules/@scope/pkg/index.js'), - ); - assert.deepEqual(nullthrows(resolved).query?.toString(), 'foo=2'); - }); - }); - - describe('aliases', function () { - it('should alias the main file using the package.browser field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-browser-alias', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( rootDir, 'node_modules', 'package-browser-alias', - 'browser.js', + 'package.json', ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/package-browser-alias', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'package.json', - ), - ], - }); + ], }); + }); - it('should alias a sub-file using the package.browser field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-browser-alias/foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( + it('should alias a sub-file using the package.browser field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-browser-alias/foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'bar.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-browser-alias', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( rootDir, 'node_modules', 'package-browser-alias', - 'bar.js', + 'package.json', ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/package-browser-alias', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'package.json', - ), - ], - }); + ], }); + }); - it('should alias a relative file using the package.browser field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './foo', - specifierType: 'esm', - parent: path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'browser.js', - ), - }); - check(resolved, { - filePath: path.join( + it('should alias a relative file using the package.browser field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './foo', + specifierType: 'esm', + parent: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'browser.js', + ), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'bar.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join( rootDir, 'node_modules', 'package-browser-alias', - 'bar.js', + 'package.json', ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'package.json', - ), - ], - }); + ], }); + }); - it('should not alias using the package.browser field with --target=node', async function () { - let resolved = await resolver.resolve({ - env: NODE_INCLUDE_ENV, - filename: 'package-browser-alias/foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( + it('should not alias using the package.browser field with --target=node', async function () { + let resolved = await resolver.resolve({ + env: NODE_INCLUDE_ENV, + filename: 'package-browser-alias/foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'foo.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-browser-alias', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( rootDir, 'node_modules', 'package-browser-alias', - 'foo.js', + 'package.json', ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/package-browser-alias', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( + ], + }); + }); + + it('should alias a deep nested relative file using the package.browser field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './nested', + specifierType: 'esm', + parent: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'browser.js', + ), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-browser-alias', + 'subfolder1/subfolder2/subfile.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join( rootDir, 'node_modules', 'package-browser-alias', - 'package.json', + 'subfolder1', + 'subfolder2', ), - ], - }); - }); - - it('should alias a deep nested relative file using the package.browser field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './nested', - specifierType: 'esm', - parent: path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'browser.js', - ), - }); - check(resolved, { - filePath: path.join( + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join( rootDir, 'node_modules', 'package-browser-alias', - 'subfolder1/subfolder2/subfile.js', + 'package.json', ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'subfolder1', - 'subfolder2', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join( - rootDir, - 'node_modules', - 'package-browser-alias', - 'package.json', - ), - ], - }); + ], }); + }); - it('should alias a sub-file using the package.alias field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-alias/foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'package-alias', - 'bar.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/package-alias', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'package-alias', 'package.json'), - ], - }); + it('should alias a sub-file using the package.alias field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-alias/foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'package-alias', 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-alias', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'package-alias', 'package.json'), + ], }); + }); - it('should alias a relative file using the package.alias field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './foo', - specifierType: 'esm', - parent: path.join( - rootDir, - 'node_modules', - 'package-alias', - 'browser.js', - ), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'package-alias', - 'bar.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'node_modules', 'package-alias', 'package.json'), - ], - }); + it('should alias a relative file using the package.alias field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './foo', + specifierType: 'esm', + parent: path.join( + rootDir, + 'node_modules', + 'package-alias', + 'browser.js', + ), }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'package-alias', 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'node_modules', 'package-alias', 'package.json'), + ], + }); + }); - it('should alias a glob using the package.alias field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './lib/test', - specifierType: 'esm', - parent: path.join( - rootDir, - 'node_modules', - 'package-alias-glob', - 'index.js', - ), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'package-alias-glob', - 'src', - 'test.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'package-alias-glob', - 'lib', - ), - }, - { - fileName: 'package.json', - aboveFilePath: path.join( - rootDir, - 'node_modules', - 'package-alias-glob', - 'src', - ), - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join( + it('should alias a glob using the package.alias field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './lib/test', + specifierType: 'esm', + parent: path.join( + rootDir, + 'node_modules', + 'package-alias-glob', + 'index.js', + ), + }); + check(resolved, { + filePath: path.join( + rootDir, + 'node_modules', + 'package-alias-glob', + 'src', + 'test.js', + ), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join( rootDir, 'node_modules', 'package-alias-glob', - 'package.json', + 'lib', ), - ], - }); + }, + { + fileName: 'package.json', + aboveFilePath: path.join( + rootDir, + 'node_modules', + 'package-alias-glob', + 'src', + ), + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join( + rootDir, + 'node_modules', + 'package-alias-glob', + 'package.json', + ), + ], }); + }); - it('should apply a module alias using the package.alias field in the root package', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliased', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/foo', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'node_modules', 'foo', 'package.json'), - ], - }); + it('should apply a module alias using the package.alias field in the root package', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliased', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/foo', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'node_modules', 'foo', 'package.json'), + ], }); + }); - it('should apply a global module alias using the package.alias field in the root package', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliased', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/foo', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'node_modules', 'foo', 'package.json'), - ], - }); + it('should apply a global module alias using the package.alias field in the root package', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliased', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'foo', 'index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/foo', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'node_modules', 'foo', 'package.json'), + ], }); + }); - it('should apply a global module alias to a sub-file in a package', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliased/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'node_modules', 'foo', 'bar.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/foo', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'node_modules', 'foo', 'package.json'), - ], - }); + it('should apply a global module alias to a sub-file in a package', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliased/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'node_modules', 'foo', 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/foo', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'node_modules', 'foo', 'package.json'), + ], }); + }); - it('should apply a module alias pointing to a file using the package.alias field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliased-file', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'bar.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should apply a module alias pointing to a file using the package.alias field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliased-file', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + check(resolved, { + filePath: path.join(rootDir, 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); - it('should apply a global module alias pointing to a file using the package.alias field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliased-file', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'bar.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should apply a global module alias pointing to a file using the package.alias field', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliased-file', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), }); + check(resolved, { + filePath: path.join(rootDir, 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); - it('should apply an alias for a virtual module folder (relative to project dir)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliasedfolder/test.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'nested', 'test.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), - }, - ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should apply an alias for a virtual module folder (relative to project dir)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliasedfolder/test.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'test.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); + + it('should apply an alias for a virtual module folder only (relative to project dir)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliasedfolder', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + { + filePath: path.join(rootDir, 'nested'), + }, + { + filePath: path.join(rootDir, 'nested.js'), + }, + { + filePath: path.join(rootDir, 'nested.jsx'), + }, + { + filePath: path.join(rootDir, 'nested.cjs'), + }, + { + filePath: path.join(rootDir, 'nested.mjs'), + }, + { + filePath: path.join(rootDir, 'nested.ts'), + }, + { + filePath: path.join(rootDir, 'nested.tsx'), + }, + { + filePath: path.join(rootDir, 'nested', 'package.json'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); + + it('should apply an alias for a virtual module folder (relative to root dir)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliasedabsolute/test.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'test.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); + + it('should apply an alias for a virtual module folder only (relative to root dir)', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'aliasedabsolute', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'index.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + { + filePath: path.join(rootDir, 'nested'), + }, + { + filePath: path.join(rootDir, 'nested.js'), + }, + { + filePath: path.join(rootDir, 'nested.jsx'), + }, + { + filePath: path.join(rootDir, 'nested.cjs'), + }, + { + filePath: path.join(rootDir, 'nested.mjs'), + }, + { + filePath: path.join(rootDir, 'nested.ts'), + }, + { + filePath: path.join(rootDir, 'nested.tsx'), + }, + { + filePath: path.join(rootDir, 'nested', 'package.json'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); + + it('should apply an alias for a virtual module folder sub-path', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'foo/bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'bar.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], + }); + }); + + it('should apply an alias for a virtual module folder glob sub-path', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'glob/bar/test', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'test.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], }); + }); - it('should apply an alias for a virtual module folder only (relative to project dir)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliasedfolder', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'nested', 'index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), - }, - { - filePath: path.join(rootDir, 'nested'), - }, - { - filePath: path.join(rootDir, 'nested.js'), - }, - { - filePath: path.join(rootDir, 'nested.jsx'), - }, - { - filePath: path.join(rootDir, 'nested.cjs'), - }, - { - filePath: path.join(rootDir, 'nested.mjs'), - }, - { - filePath: path.join(rootDir, 'nested.ts'), - }, - { - filePath: path.join(rootDir, 'nested.tsx'), - }, - { - filePath: path.join(rootDir, 'nested', 'package.json'), - }, - ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should apply an alias for a virtual module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'something', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'test.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], }); + }); - it('should apply an alias for a virtual module folder (relative to root dir)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliasedabsolute/test.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'nested', 'test.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), - }, - ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should apply a global alias for a virtual module', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'something', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), + }); + check(resolved, { + filePath: path.join(rootDir, 'nested', 'test.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'package.json', + aboveFilePath: path.join(rootDir, 'nested'), + }, + ], + invalidateOnFileChange: [path.join(rootDir, 'package.json')], }); + }); - it('should apply an alias for a virtual module folder only (relative to root dir)', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'aliasedabsolute', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'nested', 'index.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), - }, - { - filePath: path.join(rootDir, 'nested'), - }, - { - filePath: path.join(rootDir, 'nested.js'), - }, - { - filePath: path.join(rootDir, 'nested.jsx'), - }, - { - filePath: path.join(rootDir, 'nested.cjs'), - }, - { - filePath: path.join(rootDir, 'nested.mjs'), - }, - { - filePath: path.join(rootDir, 'nested.ts'), - }, - { - filePath: path.join(rootDir, 'nested.tsx'), - }, - { - filePath: path.join(rootDir, 'nested', 'package.json'), - }, - ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should resolve to an empty file when package.browser resolves to false', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-browser-exclude', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(__dirname, '..', 'src', '_empty.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-browser-exclude', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'package-browser-exclude', + 'package.json', + ), + ], }); + }); - it('should apply an alias for a virtual module folder sub-path', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'foo/bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'bar.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); + it('should resolve to an empty file when package.alias resolves to false', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-alias-exclude', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + check(resolved, { + filePath: path.join(__dirname, '..', 'src', '_empty.js'), + sideEffects: undefined, + query: undefined, + invalidateOnFileCreate: [ + { + fileName: 'node_modules/package-alias-exclude', + aboveFilePath: rootDir, + }, + ], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'package-alias-exclude', + 'package.json', + ), + ], }); + }); + }); - it('should apply an alias for a virtual module folder glob sub-path', async function () { + describe('source field', function () { + describe('package behind symlinks', function () { + it('should use the source field, when its realpath is not under `node_modules`', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'glob/bar/test', + filename: 'source', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join(rootDir, 'nested', 'test.js'), + filePath: path.join(rootDir, 'packages', 'source', 'source.js'), sideEffects: undefined, query: undefined, invalidateOnFileCreate: [ { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), + fileName: 'node_modules/source', + aboveFilePath: rootDir, }, ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'source', 'package.json'), + path.join(rootDir, 'packages', 'source', 'package.json'), + ], }); }); - it('should apply an alias for a virtual module', async function () { + it('should prioritize the source field over exports', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'something', + filename: 'source-exports', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join(rootDir, 'nested', 'test.js'), + filePath: path.join( + rootDir, + 'packages', + 'source-exports', + 'source.js', + ), sideEffects: undefined, query: undefined, invalidateOnFileCreate: [ { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), + fileName: 'node_modules/source-exports', + aboveFilePath: rootDir, }, ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], - }); - }); - - it('should apply a global alias for a virtual module', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'something', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'package-alias', 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'nested', 'test.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'package.json', - aboveFilePath: path.join(rootDir, 'nested'), - }, + invalidateOnFileChange: [ + path.join(rootDir, 'package.json'), + path.join(rootDir, 'tsconfig.json'), + path.join( + rootDir, + 'node_modules', + 'source-exports', + 'package.json', + ), + path.join(rootDir, 'packages', 'source-exports', 'package.json'), ], - invalidateOnFileChange: [path.join(rootDir, 'package.json')], }); }); - it('should resolve to an empty file when package.browser resolves to false', async function () { + it('should not use the source field, when its realpath is under `node_modules`', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-browser-exclude', + filename: 'source-pnpm', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join(__dirname, '..', 'src', '_empty.js'), + filePath: path.join( + rootDir, + 'node_modules', + '.pnpm', + 'source-pnpm@1.0.0', + 'node_modules', + 'source-pnpm', + 'dist.js', + ), sideEffects: undefined, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-browser-exclude', + fileName: 'node_modules/source-pnpm', aboveFilePath: rootDir, }, ], invalidateOnFileChange: [ path.join(rootDir, 'package.json'), path.join(rootDir, 'tsconfig.json'), + path.join(rootDir, 'node_modules', 'source-pnpm', 'package.json'), path.join( rootDir, 'node_modules', - 'package-browser-exclude', + '.pnpm', + 'source-pnpm@1.0.0', + 'node_modules', + 'source-pnpm', 'package.json', ), ], }); }); + }); - it('should resolve to an empty file when package.alias resolves to false', async function () { + describe('package not behind symlinks', function () { + it('should not use the source field', async function () { let resolved = await resolver.resolve({ env: BROWSER_ENV, - filename: 'package-alias-exclude', + filename: 'source-not-symlinked', specifierType: 'esm', parent: path.join(rootDir, 'foo.js'), }); check(resolved, { - filePath: path.join(__dirname, '..', 'src', '_empty.js'), + filePath: path.join( + rootDir, + 'node_modules', + 'source-not-symlinked', + 'dist.js', + ), sideEffects: undefined, query: undefined, invalidateOnFileCreate: [ { - fileName: 'node_modules/package-alias-exclude', + fileName: 'node_modules/source-not-symlinked', aboveFilePath: rootDir, }, ], @@ -2210,841 +2271,682 @@ const BROWSER_ENV = new Environment( path.join( rootDir, 'node_modules', - 'package-alias-exclude', + 'source-not-symlinked', 'package.json', ), ], }); }); }); + }); - describe('source field', function () { - describe('package behind symlinks', function () { - it('should use the source field, when its realpath is not under `node_modules`', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'source', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join(rootDir, 'packages', 'source', 'source.js'), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/source', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'source', 'package.json'), - path.join(rootDir, 'packages', 'source', 'package.json'), - ], - }); - }); - - it('should prioritize the source field over exports', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'source-exports', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'packages', - 'source-exports', - 'source.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/source-exports', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'source-exports', - 'package.json', - ), - path.join(rootDir, 'packages', 'source-exports', 'package.json'), - ], - }); - }); - - it('should not use the source field, when its realpath is under `node_modules`', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'source-pnpm', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - '.pnpm', - 'source-pnpm@1.0.0', - 'node_modules', - 'source-pnpm', - 'dist.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/source-pnpm', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join(rootDir, 'node_modules', 'source-pnpm', 'package.json'), - path.join( - rootDir, - 'node_modules', - '.pnpm', - 'source-pnpm@1.0.0', - 'node_modules', - 'source-pnpm', - 'package.json', - ), - ], - }); - }); - }); - - describe('package not behind symlinks', function () { - it('should not use the source field', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'source-not-symlinked', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, { - filePath: path.join( - rootDir, - 'node_modules', - 'source-not-symlinked', - 'dist.js', - ), - sideEffects: undefined, - query: undefined, - invalidateOnFileCreate: [ - { - fileName: 'node_modules/source-not-symlinked', - aboveFilePath: rootDir, - }, - ], - invalidateOnFileChange: [ - path.join(rootDir, 'package.json'), - path.join(rootDir, 'tsconfig.json'), - path.join( - rootDir, - 'node_modules', - 'source-not-symlinked', - 'package.json', - ), - ], - }); - }); + describe('package exports', function () { + it('should resolve a browser development import', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-conditions', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join( + rootDir, + 'node_modules/package-conditions/browser-import-dev.mjs', + ), + ); }); - describe('package exports', function () { - it('should resolve a browser development import', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-conditions', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join( - rootDir, - 'node_modules/package-conditions/browser-import-dev.mjs', - ), - ); - }); - - it('should resolve a browser development require', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-conditions', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join( - rootDir, - 'node_modules/package-conditions/browser-require-dev.cjs', - ), - ); - }); - - it('should resolve a browser production import', async function () { - let resolved = await prodResolver.resolve({ - env: BROWSER_ENV, - filename: 'package-conditions', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join( - rootDir, - 'node_modules/package-conditions/browser-import-prod.mjs', - ), - ); + it('should resolve a browser development require', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-conditions', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join( + rootDir, + 'node_modules/package-conditions/browser-require-dev.cjs', + ), + ); + }); - it('should resolve a browser development require', async function () { - let resolved = await prodResolver.resolve({ - env: BROWSER_ENV, - filename: 'package-conditions', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join( - rootDir, - 'node_modules/package-conditions/browser-require-prod.cjs', - ), - ); + it('should resolve a browser production import', async function () { + let resolved = await prodResolver.resolve({ + env: BROWSER_ENV, + filename: 'package-conditions', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join( + rootDir, + 'node_modules/package-conditions/browser-import-prod.mjs', + ), + ); + }); - it('should resolve a node import', async function () { - let resolved = await resolver.resolve({ - env: NODE_INCLUDE_ENV, - filename: 'package-conditions', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join(rootDir, 'node_modules/package-conditions/node-import.mjs'), - ); + it('should resolve a browser development require', async function () { + let resolved = await prodResolver.resolve({ + env: BROWSER_ENV, + filename: 'package-conditions', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join( + rootDir, + 'node_modules/package-conditions/browser-require-prod.cjs', + ), + ); + }); - it('should resolve a node require', async function () { - let resolved = await resolver.resolve({ - env: NODE_INCLUDE_ENV, - filename: 'package-conditions', - specifierType: 'commonjs', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - resolved?.filePath, - path.join( - rootDir, - 'node_modules/package-conditions/node-require.cjs', - ), - ); + it('should resolve a node import', async function () { + let resolved = await resolver.resolve({ + env: NODE_INCLUDE_ENV, + filename: 'package-conditions', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join(rootDir, 'node_modules/package-conditions/node-import.mjs'), + ); }); - describe('symlinks', function () { - it('should resolve symlinked files to their realpath', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './baz.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + it('should resolve a node require', async function () { + let resolved = await resolver.resolve({ + env: NODE_INCLUDE_ENV, + filename: 'package-conditions', + specifierType: 'commonjs', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal( + resolved?.filePath, + path.join(rootDir, 'node_modules/package-conditions/node-require.cjs'), + ); + }); + }); - it('should resolve symlinked directories to their realpath', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './symlinked-nested', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'nested', 'index.js'), - ); + describe('symlinks', function () { + it('should resolve symlinked files to their realpath', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './baz.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); }); - describe('error handling', function () { - it('should return diagnostics when package.module does not exist', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-module-fallback', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); + it('should resolve symlinked directories to their realpath', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './symlinked-nested', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'nested', 'index.js'), + ); + }); + }); - assert.equal( - nullthrows(nullthrows(result).diagnostics)[0].message, - `Could not load './module.js' from module 'package-module-fallback' found in package.json#module`, - ); + describe('error handling', function () { + it('should return diagnostics when package.module does not exist', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-module-fallback', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - it('should throw when a relative path cannot be resolved', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: './xyz.js', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); + assert.equal( + nullthrows(nullthrows(result).diagnostics)[0].message, + `Could not load './module.js' from module 'package-module-fallback' found in package.json#module`, + ); + }); - assert.equal( - nullthrows(nullthrows(result).diagnostics)[0].message, - `Cannot load file './xyz.js' in './'.`, - ); + it('should throw when a relative path cannot be resolved', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: './xyz.js', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - it('should throw when a node_module cannot be resolved', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'food', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); + assert.equal( + nullthrows(nullthrows(result).diagnostics)[0].message, + `Cannot load file './xyz.js' in './'.`, + ); + }); - assert.deepEqual(nullthrows(nullthrows(result).diagnostics)[0], { - message: `Cannot find module 'food'`, - hints: [`Did you mean '__foo__'?`], - }); + it('should throw when a node_module cannot be resolved', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'food', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - it('should throw when a subfile of a node_module cannot be resolved', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'foo/bark', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); + assert.deepEqual(nullthrows(nullthrows(result).diagnostics)[0], { + message: `Cannot find module 'food'`, + hints: [`Did you mean '__foo__'?`], + }); + }); - assert.deepEqual(nullthrows(nullthrows(result).diagnostics)[0], { - message: `Cannot load file './bark' from module 'foo'`, - hints: [`Did you mean '__foo/bar__'?`], - }); + it('should throw when a subfile of a node_module cannot be resolved', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'foo/bark', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); - it('should error when a library is missing an external dependency', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'test', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - sourcePath: path.join(rootDir, 'foo.js'), - }); + assert.deepEqual(nullthrows(nullthrows(result).diagnostics)[0], { + message: `Cannot load file './bark' from module 'foo'`, + hints: [`Did you mean '__foo/bar__'?`], + }); + }); - assert.equal( - result?.diagnostics?.[0].message, - 'External dependency "test" is not declared in package.json.', - ); + it('should error when a library is missing an external dependency', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'test', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + sourcePath: path.join(rootDir, 'foo.js'), }); - it('should not error when external dependencies are declared', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - sourcePath: path.join(rootDir, 'foo.js'), - }); + assert.equal( + result?.diagnostics?.[0].message, + 'External dependency "test" is not declared in package.json.', + ); + }); - assert.deepEqual(result, { - isExcluded: true, - invalidateOnFileChange: [], - invalidateOnFileCreate: [], - }); + it('should not error when external dependencies are declared', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + sourcePath: path.join(rootDir, 'foo.js'), }); - it('should not error when external dependencies are declared in peerDependencies', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - sourcePath: path.join(rootDir, 'foo.js'), - }); + assert.deepEqual(result, { + isExcluded: true, + invalidateOnFileChange: [], + invalidateOnFileCreate: [], + }); + }); - assert.deepEqual(result, { - isExcluded: true, - invalidateOnFileChange: [], - invalidateOnFileCreate: [], - }); + it('should not error when external dependencies are declared in peerDependencies', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + sourcePath: path.join(rootDir, 'foo.js'), }); - it('should not error on missing dependencies for environment builtins', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'atom', - specifierType: 'esm', - parent: path.join(rootDir, 'env-dep/foo.js'), - sourcePath: path.join(rootDir, 'env-dep/foo.js'), - }); + assert.deepEqual(result, { + isExcluded: true, + invalidateOnFileChange: [], + invalidateOnFileCreate: [], + }); + }); - assert.deepEqual(result, { - isExcluded: true, - invalidateOnFileChange: [], - invalidateOnFileCreate: [], - }); + it('should not error on missing dependencies for environment builtins', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'atom', + specifierType: 'esm', + parent: path.join(rootDir, 'env-dep/foo.js'), + sourcePath: path.join(rootDir, 'env-dep/foo.js'), }); - it('should not error on builtin node modules', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'buffer', - specifierType: 'esm', - parent: path.join(rootDir, 'env-dep/foo.js'), - sourcePath: path.join(rootDir, 'env-dep/foo.js'), - }); + assert.deepEqual(result, { + isExcluded: true, + invalidateOnFileChange: [], + invalidateOnFileCreate: [], + }); + }); - assert.deepEqual(result, {isExcluded: true}); + it('should not error on builtin node modules', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'buffer', + specifierType: 'esm', + parent: path.join(rootDir, 'env-dep/foo.js'), + sourcePath: path.join(rootDir, 'env-dep/foo.js'), }); - it('should error when a library has an incorrect external dependency version', async function () { - let result = await resolver.resolve({ - env: new Environment( - createEnvironment({ - context: 'browser', - isLibrary: true, - includeNodeModules: false, - }), - DEFAULT_OPTIONS, - ), - filename: 'foo', - specifierType: 'esm', - range: '^0.4.0', - parent: path.join(rootDir, 'foo.js'), - sourcePath: path.join(rootDir, 'foo.js'), - }); + assert.deepEqual(result, {isExcluded: true}); + }); - assert.equal( - result?.diagnostics?.[0].message, - 'External dependency "foo" does not satisfy required semver range "^0.4.0".', - ); + it('should error when a library has an incorrect external dependency version', async function () { + let result = await resolver.resolve({ + env: new Environment( + createEnvironment({ + context: 'browser', + isLibrary: true, + includeNodeModules: false, + }), + DEFAULT_OPTIONS, + ), + filename: 'foo', + specifierType: 'esm', + range: '^0.4.0', + parent: path.join(rootDir, 'foo.js'), + sourcePath: path.join(rootDir, 'foo.js'), }); - it('should error when package.json is invalid', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'json-error', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - let file = path.join( - rootDir, - 'node_modules', - 'json-error', - 'package.json', - ); - assert.deepEqual(result?.diagnostics, [ - { - message: 'Error parsing JSON', - codeFrames: [ - { - language: 'json', - filePath: file, - code: await overlayFS.readFile(file, 'utf8'), - codeHighlights: [ - { - message: 'expected `,` or `}` at line 3 column 3', - start: { - line: 3, - column: 3, - }, - end: { - line: 3, - column: 3, - }, + assert.equal( + result?.diagnostics?.[0].message, + 'External dependency "foo" does not satisfy required semver range "^0.4.0".', + ); + }); + + it('should error when package.json is invalid', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'json-error', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + let file = path.join( + rootDir, + 'node_modules', + 'json-error', + 'package.json', + ); + assert.deepEqual(result?.diagnostics, [ + { + message: 'Error parsing JSON', + codeFrames: [ + { + language: 'json', + filePath: file, + code: await overlayFS.readFile(file, 'utf8'), + codeHighlights: [ + { + message: 'expected `,` or `}` at line 3 column 3', + start: { + line: 3, + column: 3, }, - ], - }, - ], - }, - ]); - }); + end: { + line: 3, + column: 3, + }, + }, + ], + }, + ], + }, + ]); + }); - it('should error on an invalid empty specifier', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: '', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(result?.diagnostics, [ - { - message: 'Invalid empty specifier', - }, - ]); - }); + it('should error on an invalid empty specifier', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: '', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(result?.diagnostics, [ + { + message: 'Invalid empty specifier', + }, + ]); + }); - it('should error on unknown URL schemes', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'http://parceljs.org', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - loc: { - filePath: path.join(rootDir, 'foo.js'), - start: { - line: 1, - column: 1, - }, - end: { - line: 1, - column: 10, - }, + it('should error on unknown URL schemes', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'http://parceljs.org', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + loc: { + filePath: path.join(rootDir, 'foo.js'), + start: { + line: 1, + column: 1, }, - }); - assert.deepEqual(result?.diagnostics, [ - { - message: `Unknown url scheme or pipeline 'http:'`, + end: { + line: 1, + column: 10, }, - ]); + }, }); + assert.deepEqual(result?.diagnostics, [ + { + message: `Unknown url scheme or pipeline 'http:'`, + }, + ]); + }); - it('should error on non-exported package paths', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-exports/internal', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - let file = path.join( - rootDir, - 'node_modules/package-exports/package.json', - ); - assert.deepEqual(result?.diagnostics, [ - { - message: `Module 'package-exports/internal' is not exported from the 'package-exports' package`, - codeFrames: [ - { - language: 'json', - filePath: file, - code: await overlayFS.readFile(file, 'utf8'), - codeHighlights: [ - { - message: undefined, - start: { - line: 4, - column: 14, - }, - end: { - line: 13, - column: 3, - }, - }, - ], - }, - ], - }, - ]); + it('should error on non-exported package paths', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-exports/internal', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), }); + let file = path.join( + rootDir, + 'node_modules/package-exports/package.json', + ); + assert.deepEqual(result?.diagnostics, [ + { + message: `Module 'package-exports/internal' is not exported from the 'package-exports' package`, + codeFrames: [ + { + language: 'json', + filePath: file, + code: await overlayFS.readFile(file, 'utf8'), + codeHighlights: [ + { + message: undefined, + start: { + line: 4, + column: 14, + }, + end: { + line: 13, + column: 3, + }, + }, + ], + }, + ], + }, + ]); + }); - it('should error when export does not exist', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-exports/missing', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - assert.deepEqual(result?.diagnostics, [ - { - message: `Cannot load file './missing.mjs' from module 'package-exports'`, - hints: [], - }, - ]); - }); + it('should error when export does not exist', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-exports/missing', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + assert.deepEqual(result?.diagnostics, [ + { + message: `Cannot load file './missing.mjs' from module 'package-exports'`, + hints: [], + }, + ]); + }); - it('should error on undefined package imports', async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: '#foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.js'), - }); - let file = path.join(rootDir, 'package.json'); - assert.deepEqual(result?.diagnostics, [ - { - message: `Package import '#foo' is not defined in the 'resolver' package`, - codeFrames: [ - { - language: 'json', - filePath: file, - code: await overlayFS.readFile(file, 'utf8'), - codeHighlights: [ - { - message: undefined, - start: { - line: 15, - column: 14, - }, - end: { - line: 17, - column: 3, - }, + it('should error on undefined package imports', async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: '#foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.js'), + }); + let file = path.join(rootDir, 'package.json'); + assert.deepEqual(result?.diagnostics, [ + { + message: `Package import '#foo' is not defined in the 'resolver' package`, + codeFrames: [ + { + language: 'json', + filePath: file, + code: await overlayFS.readFile(file, 'utf8'), + codeHighlights: [ + { + message: undefined, + start: { + line: 15, + column: 14, }, - ], - }, - ], - }, - ]); - }); + end: { + line: 17, + column: 3, + }, + }, + ], + }, + ], + }, + ]); + }); - it("should error when package.json doesn't define imports field", async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: '#foo', - specifierType: 'esm', - parent: path.join(rootDir, 'node_modules', 'foo', 'foo.js'), - }); - let file = path.join(rootDir, 'node_modules', 'foo', 'package.json'); - assert.deepEqual(result?.diagnostics, [ - { - message: `Package import '#foo' is not defined in the 'foo' package`, - codeFrames: [ - { - language: 'json', - filePath: file, - code: await overlayFS.readFile(file, 'utf8'), - codeHighlights: [], - }, - ], - }, - ]); + it("should error when package.json doesn't define imports field", async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: '#foo', + specifierType: 'esm', + parent: path.join(rootDir, 'node_modules', 'foo', 'foo.js'), }); + let file = path.join(rootDir, 'node_modules', 'foo', 'package.json'); + assert.deepEqual(result?.diagnostics, [ + { + message: `Package import '#foo' is not defined in the 'foo' package`, + codeFrames: [ + { + language: 'json', + filePath: file, + code: await overlayFS.readFile(file, 'utf8'), + codeHighlights: [], + }, + ], + }, + ]); + }); - it("should error when a package.json couldn't be found", async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: '#foo', - specifierType: 'esm', - parent: path.join( - rootDir, - 'node_modules', - 'tsconfig-not-used', - 'foo.js', - ), - }); - assert.deepEqual(result?.diagnostics, [ - { - message: `Cannot find a package.json above './node\\_modules/tsconfig-not-used'`, - }, - ]); + it("should error when a package.json couldn't be found", async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: '#foo', + specifierType: 'esm', + parent: path.join( + rootDir, + 'node_modules', + 'tsconfig-not-used', + 'foo.js', + ), }); + assert.deepEqual(result?.diagnostics, [ + { + message: `Cannot find a package.json above './node\\_modules/tsconfig-not-used'`, + }, + ]); + }); - it("should error when a tsconfig.json extends couldn't be found", async function () { - let result = await resolver.resolve({ - env: BROWSER_ENV, - filename: './bar', - specifierType: 'esm', - parent: path.join( - rootDir, - 'tsconfig', - 'extends-not-found', - 'index.js', - ), - }); - let file = path.join( - rootDir, - 'tsconfig', - 'extends-not-found', - 'tsconfig.json', - ); - assert.deepEqual(result?.diagnostics, [ - { - message: 'Could not find extended tsconfig', - codeFrames: [ - { - language: 'json', - filePath: file, - code: await overlayFS.readFile(file, 'utf8'), - codeHighlights: [ - { - message: undefined, - start: { - line: 2, - column: 14, - }, - end: { - line: 2, - column: 26, - }, + it("should error when a tsconfig.json extends couldn't be found", async function () { + let result = await resolver.resolve({ + env: BROWSER_ENV, + filename: './bar', + specifierType: 'esm', + parent: path.join(rootDir, 'tsconfig', 'extends-not-found', 'index.js'), + }); + let file = path.join( + rootDir, + 'tsconfig', + 'extends-not-found', + 'tsconfig.json', + ); + assert.deepEqual(result?.diagnostics, [ + { + message: 'Could not find extended tsconfig', + codeFrames: [ + { + language: 'json', + filePath: file, + code: await overlayFS.readFile(file, 'utf8'), + codeHighlights: [ + { + message: undefined, + start: { + line: 2, + column: 14, }, - ], - }, - ], - }, - { - message: - "Cannot load file './not-found' in './tsconfig/extends-not-found'.", - hints: [], - }, - ]); - }); + end: { + line: 2, + column: 26, + }, + }, + ], + }, + ], + }, + { + message: + "Cannot load file './not-found' in './tsconfig/extends-not-found'.", + hints: [], + }, + ]); }); + }); - describe('urls', function () { - it('should ignore protocol relative urls', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '//example.com/foo.png', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, {isExcluded: true}); + describe('urls', function () { + it('should ignore protocol relative urls', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '//example.com/foo.png', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + check(resolved, {isExcluded: true}); + }); - it('should ignore hash urls', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: '#hash', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, {isExcluded: true}); + it('should ignore hash urls', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: '#hash', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + check(resolved, {isExcluded: true}); + }); - it('should ignore http: urls', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'http://example.com/foo.png', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - check(resolved, {isExcluded: true}); + it('should ignore http: urls', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'http://example.com/foo.png', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + check(resolved, {isExcluded: true}); + }); - it('should treat file: urls as absolute paths', async function () { - // TODO fixme - if (process.platform === 'win32') { - return; - } + it('should treat file: urls as absolute paths', async function () { + // TODO fixme + if (process.platform === 'win32') { + return; + } - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'file:///bar.js', - specifierType: 'url', - parent: path.join(rootDir, 'foo.js'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.js'), - ); + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'file:///bar.js', + specifierType: 'url', + parent: path.join(rootDir, 'foo.js'), }); + assert.equal(nullthrows(resolved).filePath, path.join(rootDir, 'bar.js')); }); + }); - describe('options', function () { - it('supports custom extensions', async function () { - let resolver = new NodeResolver({ - fs: overlayFS, - projectRoot: rootDir, - mode: 'development', - extensions: ['html'], - }); + describe('options', function () { + it('supports custom extensions', async function () { + let resolver = new NodeResolver({ + fs: overlayFS, + projectRoot: rootDir, + mode: 'development', + extensions: ['html'], + }); - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './bar', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.ts'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'bar.html'), - ); + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './bar', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.ts'), + }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'bar.html'), + ); - resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: './foo', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.ts'), - }); - assert.equal(nullthrows(resolved).filePath, null); + resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: './foo', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.ts'), }); + assert.equal(nullthrows(resolved).filePath, null); + }); - it('supports custom mainFields', async function () { - let resolved = await resolver.resolve({ - env: BROWSER_ENV, - filename: 'package-types', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.ts'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'node_modules', 'package-types', 'main.js'), - ); + it('supports custom mainFields', async function () { + let resolved = await resolver.resolve({ + env: BROWSER_ENV, + filename: 'package-types', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.ts'), + }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'node_modules', 'package-types', 'main.js'), + ); - let typesResolver = new NodeResolver({ - fs: overlayFS, - projectRoot: rootDir, - mode: 'development', - mainFields: ['types', 'main'], - }); + let typesResolver = new NodeResolver({ + fs: overlayFS, + projectRoot: rootDir, + mode: 'development', + mainFields: ['types', 'main'], + }); - resolved = await typesResolver.resolve({ - env: BROWSER_ENV, - filename: 'package-types', - specifierType: 'esm', - parent: path.join(rootDir, 'foo.ts'), - }); - assert.equal( - nullthrows(resolved).filePath, - path.join(rootDir, 'node_modules', 'package-types', 'types.d.ts'), - ); + resolved = await typesResolver.resolve({ + env: BROWSER_ENV, + filename: 'package-types', + specifierType: 'esm', + parent: path.join(rootDir, 'foo.ts'), }); + assert.equal( + nullthrows(resolved).filePath, + path.join(rootDir, 'node_modules', 'package-types', 'types.d.ts'), + ); }); }); }); diff --git a/packages/utils/node-resolver-rs-old/Cargo.toml b/packages/utils/node-resolver-rs-old/Cargo.toml deleted file mode 100644 index 7884b91828e..00000000000 --- a/packages/utils/node-resolver-rs-old/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -authors = ["Devon Govett "] -name = "parcel-resolver-old" -version = "0.1.0" -edition = "2021" - -[dependencies] -parcel_core = { path = "../../../crates/parcel_core" } -parcel_filesystem = { path = "../../../crates/parcel_filesystem" } - -bitflags = "1.3.2" -dashmap = "5.4.0" -elsa = "1.7.0" -glob-match = "0.2.1" -indexmap = { version = "1.9.2", features = ["serde"] } -itertools = "0.10.5" -json_comments = { path = "../../../crates/json-comments-rs" } -once_cell = "1.17.0" -parking_lot = "0.12" -percent-encoding = "2.2.0" -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.91" -thiserror = "1.0.59" -typed-arena = "2.0.2" -url = "2.3.1" -xxhash-rust = { version = "0.8.2", features = ["xxh3"] } -tracing = "0.1.40" - -[dev-dependencies] -assert_fs = "1.0" - -[target.'cfg(windows)'.dev-dependencies] -is_elevated = "0.1.2" diff --git a/packages/utils/node-resolver-rs-old/src/builtins.rs b/packages/utils/node-resolver-rs-old/src/builtins.rs deleted file mode 100644 index 16af7b5f32e..00000000000 --- a/packages/utils/node-resolver-rs-old/src/builtins.rs +++ /dev/null @@ -1,68 +0,0 @@ -// node -p "[...require('module').builtinModules].map(b => JSON.stringify(b)).join(',\n')" -pub const BUILTINS: &[&str] = &[ - "_http_agent", - "_http_client", - "_http_common", - "_http_incoming", - "_http_outgoing", - "_http_server", - "_stream_duplex", - "_stream_passthrough", - "_stream_readable", - "_stream_transform", - "_stream_wrap", - "_stream_writable", - "_tls_common", - "_tls_wrap", - "assert", - "assert/strict", - "async_hooks", - "buffer", - "child_process", - "cluster", - "console", - "constants", - "crypto", - "dgram", - "diagnostics_channel", - "dns", - "dns/promises", - "domain", - "events", - "fs", - "fs/promises", - "http", - "http2", - "https", - "inspector", - "module", - "net", - "os", - "path", - "path/posix", - "path/win32", - "perf_hooks", - "process", - "punycode", - "querystring", - "readline", - "repl", - "stream", - "stream/consumers", - "stream/promises", - "stream/web", - "string_decoder", - "sys", - "timers", - "timers/promises", - "tls", - "trace_events", - "tty", - "url", - "util", - "util/types", - "v8", - "vm", - "worker_threads", - "zlib", -]; diff --git a/packages/utils/node-resolver-rs-old/src/cache.rs b/packages/utils/node-resolver-rs-old/src/cache.rs deleted file mode 100644 index c2014b542d6..00000000000 --- a/packages/utils/node-resolver-rs-old/src/cache.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use std::ops::Deref; -use std::path::Path; -use std::path::PathBuf; - -use dashmap::DashMap; -use elsa::sync::FrozenMap; -use parking_lot::Mutex; -use typed_arena::Arena; - -use parcel_core::types::File; -use parcel_filesystem::{FileSystemRealPathCache, FileSystemRef}; - -use crate::package_json::PackageJson; -use crate::package_json::SourceField; -use crate::tsconfig::TsConfig; -use crate::tsconfig::TsConfigWrapper; -use crate::ResolverError; - -pub struct Cache { - pub fs: FileSystemRef, - /// This stores file content strings, which are borrowed when parsing package.json and tsconfig.json files. - arena: Mutex>>, - /// These map paths to parsed config files. They aren't really 'static, but Rust doens't have a good - /// way to associate a lifetime with owned data stored in the same struct. We only vend temporary references - /// from our public methods so this is ok for now. FrozenMap is an append only map, which doesn't require &mut - /// to insert into. Since each value is in a Box, it won't move and therefore references are stable. - packages: FrozenMap, ResolverError>>>, - tsconfigs: FrozenMap, ResolverError>>>, - is_file_cache: DashMap, - is_dir_cache: DashMap, - realpath_cache: FileSystemRealPathCache, -} - -impl fmt::Debug for Cache { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Cache").finish() - } -} - -#[allow(clippy::large_enum_variant)] -/// Special Cow implementation for a Cache that doesn't require Clone. -pub enum CacheCow<'a> { - Borrowed(&'a Cache), - Owned(Cache), -} - -impl<'a> Deref for CacheCow<'a> { - type Target = Cache; - - fn deref(&self) -> &Self::Target { - match self { - CacheCow::Borrowed(c) => c, - CacheCow::Owned(c) => c, - } - } -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize)] -pub struct JsonError { - pub file: File, - pub line: usize, - pub column: usize, - pub message: String, -} - -impl JsonError { - fn new(file: File, err: serde_json::Error) -> JsonError { - JsonError { - file, - line: err.line(), - column: err.column(), - message: err.to_string(), - } - } -} - -impl Cache { - pub fn new(fs: FileSystemRef) -> Self { - Self { - fs, - arena: Mutex::new(Arena::new()), - packages: FrozenMap::new(), - tsconfigs: FrozenMap::new(), - is_file_cache: DashMap::new(), - is_dir_cache: DashMap::new(), - realpath_cache: FileSystemRealPathCache::default(), - } - } - - pub fn is_file(&self, path: &Path) -> bool { - if let Some(is_file) = self.is_file_cache.get(path) { - return *is_file; - } - - let is_file = self.fs.is_file(path); - self.is_file_cache.insert(path.to_path_buf(), is_file); - is_file - } - - pub fn is_dir(&self, path: &Path) -> bool { - if let Some(is_file) = self.is_dir_cache.get(path) { - return *is_file; - } - - let is_file = self.fs.is_dir(path); - self.is_dir_cache.insert(path.to_path_buf(), is_file); - is_file - } - - pub fn canonicalize(&self, path: &Path) -> Result { - Ok(self.fs.canonicalize(path, &self.realpath_cache)?) - } - - pub fn read_package<'a>(&'a self, path: Cow) -> Result<&'a PackageJson<'a>, ResolverError> { - if let Some(pkg) = self.packages.get(path.as_ref()) { - return clone_result(pkg); - } - - fn read_package( - fs: &FileSystemRef, - realpath_cache: &FileSystemRealPathCache, - arena: &Mutex>>, - path: PathBuf, - ) -> Result, ResolverError> { - let contents: &str = read(fs, arena, &path)?; - let mut pkg = PackageJson::parse(path.clone(), contents).map_err(|e| { - JsonError::new( - File { - path, - contents: contents.into(), - }, - e, - ) - })?; - - // If the package has a `source` field, make sure - // - the package is behind symlinks - // - and the realpath to the packages does not includes `node_modules`. - // Since such package is likely a pre-compiled module - // installed with package managers, rather than including a source code. - if !matches!(pkg.source, SourceField::None) { - let realpath = fs.canonicalize(&pkg.path, realpath_cache)?; - if realpath == pkg.path - || realpath - .components() - .any(|c| c.as_os_str() == "node_modules") - { - pkg.source = SourceField::None; - } - } - - Ok(pkg) - } - - let path = path.into_owned(); - let pkg = self.packages.insert( - path.clone(), - Box::new(read_package( - &self.fs, - &self.realpath_cache, - &self.arena, - path, - )), - ); - - clone_result(pkg) - } - - pub fn read_tsconfig<'a, F: FnOnce(&mut TsConfigWrapper<'a>) -> Result<(), ResolverError>>( - &'a self, - path: &Path, - process: F, - ) -> Result<&'a TsConfigWrapper<'a>, ResolverError> { - if let Some(tsconfig) = self.tsconfigs.get(path) { - return clone_result(tsconfig); - } - - fn read_tsconfig<'a, F: FnOnce(&mut TsConfigWrapper<'a>) -> Result<(), ResolverError>>( - fs: &FileSystemRef, - arena: &Mutex>>, - path: &Path, - process: F, - ) -> Result, ResolverError> { - let data = read(fs, arena, path)?; - let contents = data.to_owned(); - let mut tsconfig = TsConfig::parse(path.to_owned(), data).map_err(|e| { - JsonError::new( - File { - contents, - path: path.to_owned(), - }, - e, - ) - })?; - // Convice the borrow checker that 'a will live as long as self and not 'static. - // Since the data is in our arena, this is true. - process(unsafe { std::mem::transmute(&mut tsconfig) })?; - Ok(tsconfig) - } - - let tsconfig = self.tsconfigs.insert( - path.to_owned(), - Box::new(read_tsconfig(&self.fs, &self.arena, path, process)), - ); - - clone_result(tsconfig) - } -} - -fn read( - fs: &FileSystemRef, - arena: &Mutex>>, - path: &Path, -) -> std::io::Result<&'static mut str> { - let arena = arena.lock(); - let data = arena.alloc(fs.read_to_string(path)?.into_boxed_str()); - // The data lives as long as the arena. In public methods, we only vend temporary references. - Ok(unsafe { &mut *(&mut **data as *mut str) }) -} - -fn clone_result(res: &Result) -> Result<&T, E> { - match res { - Ok(v) => Ok(v), - Err(err) => Err(err.clone()), - } -} diff --git a/packages/utils/node-resolver-rs-old/src/error.rs b/packages/utils/node-resolver-rs-old/src/error.rs deleted file mode 100644 index bae446bc709..00000000000 --- a/packages/utils/node-resolver-rs-old/src/error.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::path::PathBuf; -use std::sync::Arc; - -use thiserror::Error; - -use crate::cache::JsonError; -use crate::specifier::SpecifierError; -use crate::PackageJsonError; - -#[derive(Debug, Clone, PartialEq, serde::Serialize, Error)] -#[serde(tag = "type")] -pub enum ResolverError { - #[error("Unknown scheme {scheme}")] - UnknownScheme { scheme: String }, - #[error("Unknown error")] - UnknownError, - #[error("File {relative} not found from {from}")] - FileNotFound { relative: PathBuf, from: PathBuf }, - #[error("Module {module} not found")] - ModuleNotFound { module: String }, - #[error("Module {module} entry not found in path {entry_path} with package {package_path} and field {field}")] - ModuleEntryNotFound { - module: String, - entry_path: PathBuf, - package_path: PathBuf, - field: &'static str, - }, - #[error("Module {module} subpath {path} with package {package_path}")] - ModuleSubpathNotFound { - module: String, - path: PathBuf, - package_path: PathBuf, - }, - #[error("JSON error")] - JsonError(JsonError), - #[error("IO error")] - IOError(IOError), - #[error("Package JSON error. Module {module} at path {path}")] - PackageJsonError { - error: PackageJsonError, - module: String, - path: PathBuf, - }, - #[error("Package JSON not found from {from}")] - PackageJsonNotFound { from: PathBuf }, - #[error("Invalid specifier")] - InvalidSpecifier(SpecifierError), - #[error("TS config extends not found for {tsconfig}. Error {error}")] - TsConfigExtendsNotFound { - tsconfig: PathBuf, - error: Box, - }, -} - -#[derive(Debug, Clone)] -pub struct IOError(Arc); - -impl serde::Serialize for IOError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - #[derive(serde::Serialize)] - struct IOErrorMessage { - message: String, - } - - let msg = IOErrorMessage { - message: self.0.to_string(), - }; - - msg.serialize(serializer) - } -} - -impl Display for IOError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - -impl PartialEq for IOError { - fn eq(&self, other: &Self) -> bool { - self.0.kind() == other.0.kind() - } -} - -impl From<()> for ResolverError { - fn from(_: ()) -> Self { - ResolverError::UnknownError - } -} - -impl From for ResolverError { - fn from(_: std::str::Utf8Error) -> Self { - ResolverError::UnknownError - } -} - -impl From for ResolverError { - fn from(e: JsonError) -> Self { - ResolverError::JsonError(e) - } -} - -impl From for ResolverError { - fn from(e: std::io::Error) -> Self { - ResolverError::IOError(IOError(Arc::new(e))) - } -} - -impl From for ResolverError { - fn from(value: SpecifierError) -> Self { - ResolverError::InvalidSpecifier(value) - } -} diff --git a/packages/utils/node-resolver-rs-old/src/invalidations.rs b/packages/utils/node-resolver-rs-old/src/invalidations.rs deleted file mode 100644 index 097a4a297b8..00000000000 --- a/packages/utils/node-resolver-rs-old/src/invalidations.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::path::Path; -use std::path::PathBuf; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; - -use dashmap::DashSet; - -use crate::path::normalize_path; -use crate::ResolverError; - -#[derive(PartialEq, Eq, Hash, Debug, Clone)] -pub enum FileCreateInvalidation { - Path(PathBuf), - FileName { file_name: String, above: PathBuf }, - Glob(String), -} - -#[derive(Default, Debug)] -pub struct Invalidations { - pub invalidate_on_file_create: DashSet, - pub invalidate_on_file_change: DashSet, - pub invalidate_on_startup: AtomicBool, -} - -impl Invalidations { - pub fn invalidate_on_file_create(&self, path: &Path) { - self - .invalidate_on_file_create - .insert(FileCreateInvalidation::Path(normalize_path(path))); - } - - pub fn invalidate_on_file_create_above>(&self, file_name: S, above: &Path) { - self - .invalidate_on_file_create - .insert(FileCreateInvalidation::FileName { - file_name: file_name.into(), - above: normalize_path(above), - }); - } - - pub fn invalidate_on_glob_create>(&self, glob: S) { - self - .invalidate_on_file_create - .insert(FileCreateInvalidation::Glob(glob.into())); - } - - pub fn invalidate_on_file_change(&self, invalidation: &Path) { - self - .invalidate_on_file_change - .insert(normalize_path(invalidation)); - } - - pub fn invalidate_on_startup(&self) { - self.invalidate_on_startup.store(true, Ordering::Relaxed) - } - - pub fn extend(&self, other: &Invalidations) { - for f in other.invalidate_on_file_create.iter() { - self.invalidate_on_file_create.insert(f.clone()); - } - - for f in other.invalidate_on_file_change.iter() { - self.invalidate_on_file_change.insert(f.clone()); - } - - if other.invalidate_on_startup.load(Ordering::Relaxed) { - self.invalidate_on_startup(); - } - } - - pub fn read Result>( - &self, - path: &Path, - f: F, - ) -> Result { - match f() { - Ok(v) => { - self.invalidate_on_file_change(path); - Ok(v) - } - Err(e) => { - if matches!(e, ResolverError::IOError(..)) { - self.invalidate_on_file_create(path); - } - Err(e) - } - } - } -} diff --git a/packages/utils/node-resolver-rs-old/src/lib.rs b/packages/utils/node-resolver-rs-old/src/lib.rs deleted file mode 100644 index c491740f9c6..00000000000 --- a/packages/utils/node-resolver-rs-old/src/lib.rs +++ /dev/null @@ -1,2816 +0,0 @@ -use std::borrow::Cow; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; - -use bitflags::bitflags; -use once_cell::unsync::OnceCell; -use package_json::AliasValue; -use package_json::ExportsResolution; -use package_json::PackageJson; -pub use parcel_core::types::IncludeNodeModules; -use tsconfig::TsConfig; - -mod builtins; -mod cache; -mod error; -mod invalidations; -mod package_json; -mod path; -mod specifier; -mod tsconfig; -mod url_to_path; - -pub use cache::Cache; -pub use cache::CacheCow; -pub use error::ResolverError; -pub use invalidations::*; -pub use package_json::ExportsCondition; -pub use package_json::Fields; -pub use package_json::ModuleType; -pub use package_json::PackageJsonError; -#[cfg(not(target_arch = "wasm32"))] -pub use parcel_filesystem::os_file_system::OsFileSystem; -pub use parcel_filesystem::FileSystem; -pub use specifier::parse_package_specifier; -pub use specifier::parse_scheme; -pub use specifier::Specifier; -pub use specifier::SpecifierError; -pub use specifier::SpecifierType; - -use crate::path::resolve_path; - -bitflags! { - pub struct Flags: u16 { - /// Parcel-style absolute paths resolved relative to project root. - const ABSOLUTE_SPECIFIERS = 1 << 0; - /// Parcel-style tilde specifiers resolved relative to nearest module root. - const TILDE_SPECIFIERS = 1 << 1; - /// The `npm:` scheme. - const NPM_SCHEME = 1 << 2; - /// The "alias" field in package.json. - const ALIASES = 1 << 3; - /// The settings in tsconfig.json. - const TSCONFIG = 1 << 4; - /// The "exports" and "imports" fields in package.json. - const EXPORTS = 1 << 5; - /// Directory index files, e.g. index.js. - const DIR_INDEX = 1 << 6; - /// Optional extensions in specifiers, using the `extensions` setting. - const OPTIONAL_EXTENSIONS = 1 << 7; - /// Whether extensions are replaced in specifiers, e.g. `./foo.js` -> `./foo.ts`. - /// This also allows omitting the `.ts` and `.tsx` extensions when outside node_modules. - const TYPESCRIPT_EXTENSIONS = 1 << 8; - /// Whether to allow omitting the extension when resolving the same file type. - const PARENT_EXTENSION = 1 << 9; - /// Whether to allow optional extensions in the "exports" field. - const EXPORTS_OPTIONAL_EXTENSIONS = 1 << 10; - - /// Default Node settings for CommonJS. - const NODE_CJS = Self::EXPORTS.bits | Self::DIR_INDEX.bits | Self::OPTIONAL_EXTENSIONS.bits; - /// Default Node settings for ESM. - const NODE_ESM = Self::EXPORTS.bits; - /// Default TypeScript settings. - const TYPESCRIPT = Self::TSCONFIG.bits | Self::EXPORTS.bits | Self::DIR_INDEX.bits | Self::OPTIONAL_EXTENSIONS.bits | Self::TYPESCRIPT_EXTENSIONS.bits | Self::EXPORTS_OPTIONAL_EXTENSIONS.bits; - } -} - -type ResolveModuleDir = dyn Fn(&str, &Path) -> Result + Send + Sync; - -pub struct Resolver<'a> { - pub project_root: Cow<'a, Path>, - pub extensions: Extensions<'a>, - pub index_file: &'a str, - pub entries: Fields, - pub flags: Flags, - pub include_node_modules: Cow<'a, IncludeNodeModules>, - pub conditions: ExportsCondition, - pub module_dir_resolver: Option>, - pub cache: CacheCow<'a>, -} - -pub enum Extensions<'a> { - Borrowed(&'a [&'a str]), - Owned(Vec), -} - -impl<'a> Extensions<'a> { - fn iter(&self) -> impl Iterator { - match self { - Extensions::Borrowed(v) => itertools::Either::Left(v.iter().copied()), - Extensions::Owned(v) => itertools::Either::Right(v.iter().map(|s| s.as_str())), - } - } -} - -#[derive(Default, Debug)] -pub struct ResolveOptions { - pub conditions: ExportsCondition, - pub custom_conditions: Vec, -} - -#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] -#[serde(tag = "type", content = "value")] -pub enum Resolution { - /// Resolved to a file path. - Path(PathBuf), - /// Resolved to a runtime builtin module. - Builtin(String), - /// Resolved to an external module that should not be bundled. - External, - /// Resolved to an empty module (e.g. `false` in the package.json#browser field). - Empty, - /// Resolved to a global variable. - Global(String), -} - -pub struct ResolveResult { - pub result: Result<(Resolution, Option), ResolverError>, - pub invalidations: Invalidations, -} - -impl<'a> Resolver<'a> { - pub fn node(project_root: Cow<'a, Path>, cache: CacheCow<'a>) -> Self { - Self { - project_root, - extensions: Extensions::Borrowed(&["js", "json", "node"]), - index_file: "index", - entries: Fields::MAIN, - flags: Flags::NODE_CJS, - cache, - include_node_modules: Cow::Owned(IncludeNodeModules::default()), - conditions: ExportsCondition::NODE, - module_dir_resolver: None, - } - } - - pub fn node_esm(project_root: Cow<'a, Path>, cache: CacheCow<'a>) -> Self { - Self { - project_root, - extensions: Extensions::Borrowed(&[]), - index_file: "index", - entries: Fields::MAIN, - flags: Flags::NODE_ESM, - cache, - include_node_modules: Cow::Owned(IncludeNodeModules::default()), - conditions: ExportsCondition::NODE, - module_dir_resolver: None, - } - } - - pub fn parcel(project_root: Cow<'a, Path>, cache: CacheCow<'a>) -> Self { - Self { - project_root, - extensions: Extensions::Borrowed(&["mjs", "js", "jsx", "cjs", "json"]), - index_file: "index", - entries: Fields::MAIN | Fields::SOURCE | Fields::BROWSER | Fields::MODULE, - flags: Flags::all(), - cache, - include_node_modules: Cow::Owned(IncludeNodeModules::default()), - conditions: ExportsCondition::empty(), - module_dir_resolver: None, - } - } - - pub fn resolve( - &self, - specifier: &str, - from: &Path, - specifier_type: SpecifierType, - ) -> ResolveResult { - self.resolve_with_options(specifier, from, specifier_type, Default::default()) - } - - pub fn resolve_with_options( - &self, - specifier: &str, - from: &Path, - specifier_type: SpecifierType, - options: ResolveOptions, - ) -> ResolveResult { - tracing::trace!(%specifier, ?from, ?specifier_type, "Resolving specifier"); - let invalidations = Invalidations::default(); - let result = - self.resolve_with_invalidations(specifier, from, specifier_type, &invalidations, options); - - ResolveResult { - result, - invalidations, - } - } - - pub fn resolve_with_invalidations( - &self, - specifier: &str, - from: &Path, - specifier_type: SpecifierType, - invalidations: &Invalidations, - options: ResolveOptions, - ) -> Result<(Resolution, Option), ResolverError> { - let (specifier, query) = match Specifier::parse(specifier, specifier_type, self.flags) { - Ok(s) => s, - Err(e) => return Err(e.into()), - }; - let mut request = ResolveRequest::new(self, &specifier, specifier_type, from, invalidations); - if !options.conditions.is_empty() || !options.custom_conditions.is_empty() { - // If custom conditions are defined, these override the default conditions inferred from the specifier type. - request.conditions = self.conditions | options.conditions; - request.custom_conditions = options.custom_conditions.as_slice(); - } - - match request.resolve() { - Ok(r) => Ok((r, query.map(|q| q.to_owned()))), - Err(r) => Err(r), - } - } - - pub fn resolve_side_effects( - &self, - path: &Path, - invalidations: &Invalidations, - ) -> Result { - if let Some(package) = self.find_package(path.parent().unwrap(), invalidations)? { - Ok(package.has_side_effects(path)) - } else { - Ok(true) - } - } - - pub fn resolve_module_type( - &self, - path: &Path, - invalidations: &Invalidations, - ) -> Result { - if let Some(ext) = path.extension() { - if ext == "mjs" { - return Ok(ModuleType::Module); - } - - if ext == "cjs" || ext == "node" { - return Ok(ModuleType::CommonJs); - } - - if ext == "json" { - return Ok(ModuleType::Json); - } - - if ext == "js" { - if let Some(package) = self.find_package(path.parent().unwrap(), invalidations)? { - return Ok(package.module_type); - } - } - } - - Ok(ModuleType::CommonJs) - } - - fn find_package( - &self, - from: &Path, - invalidations: &Invalidations, - ) -> Result, ResolverError> { - if let Some(path) = self.find_ancestor_file(from, "package.json", invalidations) { - let package = self.cache.read_package(Cow::Owned(path))?; - return Ok(Some(package)); - } - - Ok(None) - } - - fn find_ancestor_file( - &self, - from: &Path, - filename: &str, - invalidations: &Invalidations, - ) -> Option { - let mut first = true; - for dir in from.ancestors() { - if let Some(filename) = dir.file_name() { - if filename == "node_modules" { - break; - } - } - - let file = dir.join(filename); - if self.cache.is_file(&file) { - invalidations.invalidate_on_file_change(&file); - return Some(file); - } - - if dir == self.project_root { - break; - } - - if first { - invalidations.invalidate_on_file_create_above(filename, from); - } - - first = false; - } - - None - } -} - -struct ResolveRequest<'a> { - resolver: &'a Resolver<'a>, - specifier: &'a Specifier<'a>, - specifier_type: SpecifierType, - from: &'a Path, - flags: RequestFlags, - tsconfig: OnceCell>>, - root_package: OnceCell>>, - invalidations: &'a Invalidations, - conditions: ExportsCondition, - custom_conditions: &'a [String], - priority_extension: Option<&'a str>, -} - -bitflags! { - struct RequestFlags: u8 { - const IN_TS_FILE = 1 << 0; - const IN_JS_FILE = 1 << 1; - const IN_NODE_MODULES = 1 << 2; - } -} - -impl<'a> ResolveRequest<'a> { - fn new( - resolver: &'a Resolver<'a>, - specifier: &'a Specifier<'a>, - mut specifier_type: SpecifierType, - from: &'a Path, - invalidations: &'a Invalidations, - ) -> Self { - let mut flags = RequestFlags::empty(); - if let Some(ext) = from.extension() { - if ext == "ts" || ext == "tsx" || ext == "mts" || ext == "cts" { - flags |= RequestFlags::IN_TS_FILE; - } else if ext == "js" || ext == "jsx" || ext == "mjs" || ext == "cjs" { - flags |= RequestFlags::IN_JS_FILE; - } - } - - if from.components().any(|c| c.as_os_str() == "node_modules") { - flags |= RequestFlags::IN_NODE_MODULES; - } - - // Replace the specifier type for `npm:` URLs so we resolve it like a module. - if specifier_type == SpecifierType::Url && matches!(specifier, Specifier::Package(..)) { - specifier_type = SpecifierType::Esm; - } - - // Add "import" or "require" condition to global conditions based on specifier type. - // Also add the "module" condition if the "module" entry field is enabled. - let mut conditions = resolver.conditions; - let module_condition = if resolver.entries.contains(Fields::MODULE) { - ExportsCondition::MODULE - } else { - ExportsCondition::empty() - }; - match specifier_type { - SpecifierType::Esm => conditions |= ExportsCondition::IMPORT | module_condition, - SpecifierType::Cjs => conditions |= ExportsCondition::REQUIRE | module_condition, - _ => {} - } - - // Store the parent file extension so we can prioritize it even in sub-requests. - let priority_extension = if resolver.flags.contains(Flags::PARENT_EXTENSION) { - from.extension().and_then(|ext| ext.to_str()) - } else { - None - }; - - Self { - resolver, - specifier, - specifier_type, - from, - flags, - tsconfig: OnceCell::new(), - root_package: OnceCell::new(), - invalidations, - conditions, - custom_conditions: &[], - priority_extension, - } - } - - fn resolve_aliases( - &self, - package: &PackageJson, - specifier: &Specifier, - fields: Fields, - ) -> Result, ResolverError> { - // Don't resolve alias if it came from the package.json itself (i.e. another alias). - if self.from == package.path { - return Ok(None); - } - - match package.resolve_aliases(specifier, fields) { - Some(alias) => match alias.as_ref() { - AliasValue::Specifier(specifier) => { - let mut req = ResolveRequest::new( - self.resolver, - specifier, - SpecifierType::Cjs, - &package.path, - self.invalidations, - ); - req.priority_extension = self.priority_extension; - req.conditions = self.conditions; - req.custom_conditions = self.custom_conditions; - let resolved = req.resolve()?; - Ok(Some(resolved)) - } - AliasValue::Bool(false) => Ok(Some(Resolution::Empty)), - AliasValue::Bool(true) => Ok(None), - AliasValue::Global { global } => Ok(Some(Resolution::Global((*global).to_owned()))), - }, - None => Ok(None), - } - } - - fn root_package(&self) -> Result<&Option<&PackageJson>, ResolverError> { - self - .root_package - .get_or_try_init(|| self.find_package(&self.resolver.project_root)) - } - - fn resolve(&self) -> Result { - match &self.specifier { - Specifier::Relative(specifier) => { - // Relative path - self.resolve_relative(specifier, self.from) - } - Specifier::Tilde(specifier) if self.resolver.flags.contains(Flags::TILDE_SPECIFIERS) => { - // Tilde path. Resolve relative to nearest node_modules directory, - // the nearest directory with package.json or the project root - whichever comes first. - if let Some(p) = self.find_ancestor_file(self.from, "package.json") { - return self.resolve_relative(specifier, &p); - } - - Err(ResolverError::PackageJsonNotFound { - from: self.from.to_owned(), - }) - } - Specifier::Absolute(specifier) => { - // In Parcel mode, absolute paths are actually relative to the project root. - if self.resolver.flags.contains(Flags::ABSOLUTE_SPECIFIERS) { - self.resolve_relative( - specifier.strip_prefix("/").unwrap(), - &self.resolver.project_root.join("index"), - ) - } else if let Some(res) = self.load_path(specifier, None)? { - Ok(res) - } else { - Err(ResolverError::FileNotFound { - relative: specifier.as_ref().to_owned(), - from: PathBuf::from("/"), - }) - } - } - Specifier::Hash(hash) => { - if self.specifier_type == SpecifierType::Url { - // An ID-only URL, e.g. `url(#clip-path)` for CSS rules. Ignore. - Ok(Resolution::External) - } else if self.specifier_type == SpecifierType::Esm - && self.resolver.flags.contains(Flags::EXPORTS) - { - // An internal package #import specifier. - let package = self.find_package(self.from.parent().unwrap_or_else(|| self.from))?; - if let Some(package) = package { - let res = package - .resolve_package_imports(hash, self.conditions, self.custom_conditions) - .map_err(|error| ResolverError::PackageJsonError { - error, - module: package.name.to_owned(), - path: package.path.clone(), - })?; - match res { - ExportsResolution::Path(path) => { - // Extensionless specifiers are not supported in the imports field. - if let Some(res) = self.try_file_without_aliases(&path)? { - return Ok(res); - } - } - ExportsResolution::Package(specifier) => { - let (module, subpath) = parse_package_specifier(&specifier)?; - // TODO: should this follow aliases?? - return self.resolve_bare(module, subpath); - } - _ => {} - } - } - - Err(ResolverError::PackageJsonNotFound { - from: self.from.to_owned(), - }) - } else { - Err(ResolverError::UnknownError) - } - } - Specifier::Package(module, subpath) => { - // Bare specifier. - self.resolve_bare(module, subpath) - } - Specifier::Builtin(builtin) => { - if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(self.specifier)? { - return Ok(res); - } - Ok(Resolution::Builtin(builtin.as_ref().to_owned())) - } - Specifier::Url(url) => { - if self.specifier_type == SpecifierType::Url { - Ok(Resolution::External) - } else { - let (scheme, _) = parse_scheme(url)?; - Err(ResolverError::UnknownScheme { - scheme: scheme.into_owned(), - }) - } - } - _ => Err(ResolverError::UnknownError), - } - } - - fn find_ancestor_file(&self, from: &Path, filename: &str) -> Option { - let from = from.parent().unwrap(); - self - .resolver - .find_ancestor_file(from, filename, self.invalidations) - } - - fn find_package(&self, from: &Path) -> Result>, ResolverError> { - self.resolver.find_package(from, self.invalidations) - } - - fn resolve_relative(&self, specifier: &Path, from: &Path) -> Result { - // Resolve aliases from the nearest package.json. - let path = resolve_path(from, specifier); - let package = if self.resolver.flags.contains(Flags::ALIASES) { - self.find_package(path.parent().unwrap())? - } else { - None - }; - - if let Some(res) = self.load_path(&path, package)? { - return Ok(res); - } - - Err(ResolverError::FileNotFound { - relative: specifier.to_owned(), - from: from.to_owned(), - }) - } - - fn resolve_bare(&self, module: &str, subpath: &str) -> Result { - let include = match self.resolver.include_node_modules.as_ref() { - IncludeNodeModules::Bool(b) => *b, - IncludeNodeModules::Array(a) => a.iter().any(|v| v == module), - IncludeNodeModules::Map(m) => *m.get(module).unwrap_or(&true), - }; - - if !include { - return Ok(Resolution::External); - } - - // Try aliases and tsconfig paths first. - let specifier = Specifier::Package(Cow::Borrowed(module), Cow::Borrowed(subpath)); - if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(&specifier)? { - return Ok(res); - } - - self.resolve_node_module(module, subpath) - } - - fn resolve_package_aliases_and_tsconfig_paths( - &self, - specifier: &Specifier, - ) -> Result, ResolverError> { - if self.resolver.flags.contains(Flags::ALIASES) { - // First, check for an alias in the root package.json. - if let Some(package) = self.root_package()? { - if let Some(res) = self.resolve_aliases(package, specifier, Fields::ALIAS)? { - return Ok(Some(res)); - } - } - - // Next, try the local package.json. - if let Some(package) = self.find_package(self.from.parent().unwrap_or_else(|| self.from))? { - let mut fields = Fields::ALIAS; - if self.resolver.entries.contains(Fields::BROWSER) { - fields |= Fields::BROWSER; - } - if let Some(res) = self.resolve_aliases(package, specifier, fields)? { - return Ok(Some(res)); - } - } - } - - // Next, check tsconfig.json for the paths and baseUrl options. - self.resolve_tsconfig_paths() - } - - fn resolve_node_module(&self, module: &str, subpath: &str) -> Result { - // If there is a custom module directory resolver (e.g. Yarn PnP), use that. - if let Some(module_dir_resolver) = &self.resolver.module_dir_resolver { - let package_dir = module_dir_resolver(module, self.from)?; - return self.resolve_package(package_dir, module, subpath); - } else { - self.invalidations.invalidate_on_file_create_above( - format!("node_modules/{}", module), - self.from.parent().unwrap_or_else(|| self.from), - ); - - for dir in self.from.ancestors() { - // Skip over node_modules directories - if let Some(filename) = dir.file_name() { - if filename == "node_modules" { - continue; - } - } - - let package_dir = dir.join("node_modules").join(module); - if self.resolver.cache.is_dir(&package_dir) { - return self.resolve_package(package_dir, module, subpath); - } - } - } - - // NODE_PATH?? - - Err(ResolverError::ModuleNotFound { - module: module.to_owned(), - }) - } - - fn resolve_package( - &self, - mut package_dir: PathBuf, - module: &str, - subpath: &str, - ) -> Result { - let package_path = package_dir.join("package.json"); - let package = self.invalidations.read(&package_path, || { - self - .resolver - .cache - .read_package(Cow::Borrowed(&package_path)) - }); - - let package = match package { - Ok(package) => package, - Err(ResolverError::IOError(_)) => { - // No package.json in node_modules is probably invalid but we have tests for it... - if self.resolver.flags.contains(Flags::DIR_INDEX) { - if let Some(res) = self.load_file(&package_dir.join(self.resolver.index_file), None)? { - return Ok(res); - } - } - - return Err(ResolverError::ModuleNotFound { - module: module.to_owned(), - }); - } - Err(err) => return Err(err), - }; - - // Try the "source" field first, if present. - if self.resolver.entries.contains(Fields::SOURCE) && subpath.is_empty() { - if let Some(source) = package.source() { - if let Some(res) = self.load_path(&source, Some(package))? { - return Ok(res); - } - } - } - - // If the exports field is present, use the Node ESM algorithm. - // Otherwise, fall back to classic CJS resolution. - if self.resolver.flags.contains(Flags::EXPORTS) && package.has_exports() { - let path = package - .resolve_package_exports(subpath, self.conditions, self.custom_conditions) - .map_err(|e| ResolverError::PackageJsonError { - module: package.name.to_owned(), - path: package.path.clone(), - error: e, - })?; - - // Extensionless specifiers are not supported in the exports field - // according to the Node spec (for both ESM and CJS). However, webpack - // didn't follow this, so there are many packages that rely on it (e.g. underscore). - if self - .resolver - .flags - .contains(Flags::EXPORTS_OPTIONAL_EXTENSIONS) - { - if let Some(res) = self.load_file(&path, Some(package))? { - return Ok(res); - } - } else if let Some(res) = self.try_file_without_aliases(&path)? { - return Ok(res); - } - - // TODO: track location of resolved field - Err(ResolverError::ModuleSubpathNotFound { - module: module.to_owned(), - path, - package_path: package.path.clone(), - }) - } else if !subpath.is_empty() { - package_dir.push(subpath); - if let Some(res) = self.load_path(&package_dir, Some(package))? { - return Ok(res); - } - - Err(ResolverError::ModuleSubpathNotFound { - module: module.to_owned(), - path: package_dir, - package_path: package.path.clone(), - }) - } else { - let res = self.try_package_entries(package); - if let Ok(Some(res)) = res { - return Ok(res); - } - - // Node ESM doesn't allow directory imports. - if self.resolver.flags.contains(Flags::DIR_INDEX) { - if let Some(res) = - self.load_file(&package_dir.join(self.resolver.index_file), Some(package))? - { - return Ok(res); - } - } - - res?; - - Err(ResolverError::ModuleSubpathNotFound { - module: module.to_owned(), - path: package_dir.join(self.resolver.index_file), - package_path: package.path.clone(), - }) - } - } - - fn try_package_entries( - &self, - package: &PackageJson, - ) -> Result, ResolverError> { - // Try all entry fields. - if let Some((entry, field)) = package.entries(self.resolver.entries).next() { - if let Some(res) = self.load_path(&entry, Some(package))? { - return Ok(Some(res)); - } else { - return Err(ResolverError::ModuleEntryNotFound { - module: package.name.to_owned(), - entry_path: entry, - package_path: package.path.clone(), - field, - }); - } - } - - Ok(None) - } - - fn load_path( - &self, - path: &Path, - package: Option<&PackageJson>, - ) -> Result, ResolverError> { - // Urls and Node ESM do not resolve directory index files. - let can_load_directory = - self.resolver.flags.contains(Flags::DIR_INDEX) && self.specifier_type != SpecifierType::Url; - - // If path ends with / only try loading as a directory. - let is_directory = can_load_directory - && path - .as_os_str() - .to_str() - .map(|s| s.ends_with('/')) - .unwrap_or(false); - - if !is_directory { - if let Some(res) = self.load_file(path, package)? { - return Ok(Some(res)); - } - } - - // Urls and Node ESM do not resolve directory index files. - if can_load_directory { - return self.load_directory(path, package); - } - - Ok(None) - } - - fn load_file( - &self, - path: &Path, - package: Option<&PackageJson>, - ) -> Result, ResolverError> { - // First try the path as is. - // TypeScript only supports resolving specifiers ending with `.ts` or `.tsx` - // in a certain mode, but we always allow it. - // If there is no extension in the original specifier, only check aliases - // here and delay checking for an extensionless file until later (since this is unlikely). - if let Some(res) = self.try_suffixes(path, "", package, path.extension().is_none())? { - return Ok(Some(res)); - } - - // TypeScript allows a specifier like "./foo.js" to resolve to "./foo.ts". - // TSC does this before trying to append an extension. We match this - // rather than matching "./foo.js.ts", which seems more unlikely. - // However, if "./foo.js" exists we will resolve to it (above), unlike TSC. - // This is to match Node and other bundlers. - if self.resolver.flags.contains(Flags::TYPESCRIPT_EXTENSIONS) - && self.flags.contains(RequestFlags::IN_TS_FILE) - && !self.flags.contains(RequestFlags::IN_NODE_MODULES) - && self.specifier_type != SpecifierType::Url - { - if let Some(ext) = path.extension() { - // TODO: would be nice if there was a way to do this without cloning - // but OsStr doesn't let you create a slice. - let without_extension = &path.with_extension(""); - let extensions: Option<&[&str]> = if ext == "js" || ext == "jsx" { - // TSC always prioritizes .ts over .tsx, even when the original extension was .jsx. - Some(&["ts", "tsx"]) - } else if ext == "mjs" { - Some(&["mts"]) - } else if ext == "cjs" { - Some(&["cts"]) - } else { - None - }; - - let res = if let Some(extensions) = extensions { - self.try_extensions( - without_extension, - package, - &Extensions::Borrowed(extensions), - false, - )? - } else { - None - }; - - if res.is_some() { - return Ok(res); - } - } - } - - // Try adding the same extension as in the parent file first. - if let Some(ext) = self.priority_extension { - // Use try_suffixes here to skip the specifier_type check. - // This is reproducing a bug in the old version of the Parcel resolver - // where URL dependencies could omit the extension if it was the same as the parent. - // TODO: Revert this in the next major version. - if let Some(res) = self.try_suffixes(path, ext, package, false)? { - return Ok(Some(res)); - } - } - - // Try adding typescript extensions if outside node_modules. - if self - .resolver - .flags - .contains(Flags::TYPESCRIPT_EXTENSIONS | Flags::OPTIONAL_EXTENSIONS) - && !self.flags.contains(RequestFlags::IN_NODE_MODULES) - { - if let Some(res) = - self.try_extensions(path, package, &Extensions::Borrowed(&["ts", "tsx"]), true)? - { - return Ok(Some(res)); - } - } - - // Try appending the configured extensions. - if let Some(res) = self.try_extensions(path, package, &self.resolver.extensions, true)? { - return Ok(Some(res)); - } - - // If there is no extension in the specifier, try an extensionless file as a last resort. - if path.extension().is_none() { - if let Some(res) = self.try_suffixes(path, "", package, false)? { - return Ok(Some(res)); - } - } - - Ok(None) - } - - fn try_extensions( - &self, - path: &Path, - package: Option<&PackageJson>, - extensions: &Extensions, - skip_parent: bool, - ) -> Result, ResolverError> { - if self.resolver.flags.contains(Flags::OPTIONAL_EXTENSIONS) - && self.specifier_type != SpecifierType::Url - { - // Try appending each extension. - for ext in extensions.iter() { - // Skip parent extension if we already tried it. - if skip_parent - && self.resolver.flags.contains(Flags::PARENT_EXTENSION) - && matches!(self.from.extension(), Some(e) if e == ext) - { - continue; - } - - if let Some(res) = self.try_suffixes(path, ext, package, false)? { - return Ok(Some(res)); - } - } - } - - Ok(None) - } - - fn try_suffixes( - &self, - path: &Path, - ext: &str, - package: Option<&PackageJson>, - alias_only: bool, - ) -> Result, ResolverError> { - // TypeScript supports a moduleSuffixes option in tsconfig.json which allows suffixes - // such as ".ios" to be appended just before the last extension. - let module_suffixes = self - .tsconfig()? - .and_then(|tsconfig| tsconfig.module_suffixes.as_ref()) - .map_or([""].as_slice(), |v| v.as_slice()); - - for suffix in module_suffixes { - let mut p = if !suffix.is_empty() { - // The suffix is placed before the _last_ extension. If we will be appending - // another extension later, then we only need to append the suffix first. - // Otherwise, we need to remove the original extension so we can add the suffix. - // TODO: TypeScript only removes certain extensions here... - let original_ext = path.extension(); - let mut s = if ext.is_empty() && original_ext.is_some() { - path.with_extension("").into_os_string() - } else { - path.into() - }; - - // Append the suffix (this is not necessarily an extension). - s.push(suffix); - - // Re-add the original extension if we removed it earlier. - if ext.is_empty() { - if let Some(original_ext) = original_ext { - s.push("."); - s.push(original_ext); - } - } - - Cow::Owned(PathBuf::from(s)) - } else { - Cow::Borrowed(path) - }; - - if !ext.is_empty() { - // Append the extension. - let mut s = p.into_owned().into_os_string(); - s.push("."); - s.push(ext); - p = Cow::Owned(PathBuf::from(s)); - } - - if let Some(res) = self.try_file(p.as_ref(), package, alias_only)? { - return Ok(Some(res)); - } - } - - Ok(None) - } - - fn try_file( - &self, - path: &Path, - package: Option<&PackageJson>, - alias_only: bool, - ) -> Result, ResolverError> { - if self.resolver.flags.contains(Flags::ALIASES) { - // Check the project root package.json first. - if let Some(package) = self.root_package()? { - if let Ok(s) = path.strip_prefix(package.path.parent().unwrap()) { - let specifier = Specifier::Relative(Cow::Borrowed(s)); - if let Some(res) = self.resolve_aliases(package, &specifier, Fields::ALIAS)? { - return Ok(Some(res)); - } - } - } - - // Next try the local package.json. - if let Some(package) = package { - if let Ok(s) = path.strip_prefix(package.path.parent().unwrap()) { - let specifier = Specifier::Relative(Cow::Borrowed(s)); - let mut fields = Fields::ALIAS; - if self.resolver.entries.contains(Fields::BROWSER) { - fields |= Fields::BROWSER; - } - if let Some(res) = self.resolve_aliases(package, &specifier, fields)? { - return Ok(Some(res)); - } - } - } - } - - if alias_only { - return Ok(None); - } - - self.try_file_without_aliases(path) - } - - fn try_file_without_aliases(&self, path: &Path) -> Result, ResolverError> { - if self.resolver.cache.is_file(path) { - Ok(Some(Resolution::Path( - self.resolver.cache.canonicalize(path)?, - ))) - } else { - self.invalidations.invalidate_on_file_create(path); - Ok(None) - } - } - - fn load_directory( - &self, - dir: &Path, - parent_package: Option<&PackageJson>, - ) -> Result, ResolverError> { - // Check if there is a package.json in this directory, and if so, use its entries. - // Note that the "exports" field is NOT used here - only in resolve_node_module. - let path = dir.join("package.json"); - let mut res = Ok(None); - let package = if let Ok(package) = self.invalidations.read(&path, || { - self.resolver.cache.read_package(Cow::Borrowed(&path)) - }) { - res = self.try_package_entries(package); - if matches!(res, Ok(Some(_))) { - return res; - } - Some(package) - } else { - None - }; - - // If no package.json, or no entries, try an index file with all possible extensions. - if self.resolver.flags.contains(Flags::DIR_INDEX) && self.resolver.cache.is_dir(dir) { - return self.load_file( - &dir.join(self.resolver.index_file), - package.or(parent_package), - ); - } - - res - } - - fn resolve_tsconfig_paths(&self) -> Result, ResolverError> { - if let Some(tsconfig) = self.tsconfig()? { - for path in tsconfig.paths(self.specifier) { - // TODO: should aliases apply to tsconfig paths?? - if let Some(res) = self.load_path(&path, None)? { - return Ok(Some(res)); - } - } - } - - Ok(None) - } - - fn tsconfig(&self) -> Result<&Option<&TsConfig>, ResolverError> { - if self.resolver.flags.contains(Flags::TSCONFIG) - && self - .flags - .intersects(RequestFlags::IN_TS_FILE | RequestFlags::IN_JS_FILE) - && !self.flags.contains(RequestFlags::IN_NODE_MODULES) - { - self.tsconfig.get_or_try_init(|| { - if let Some(path) = self.find_ancestor_file(self.from, "tsconfig.json") { - let tsconfig = self.read_tsconfig(path)?; - return Ok(Some(tsconfig)); - } - - Ok(None) - }) - } else { - Ok(&None) - } - } - - fn read_tsconfig(&self, path: PathBuf) -> Result<&'a TsConfig<'a>, ResolverError> { - let tsconfig = self.invalidations.read(&path, || { - self.resolver.cache.read_tsconfig(&path, |tsconfig| { - for i in 0..tsconfig.extends.len() { - let path = match &tsconfig.extends[i] { - Specifier::Absolute(path) => path.as_ref().to_owned(), - Specifier::Relative(path) => { - let mut absolute_path = resolve_path(&tsconfig.compiler_options.path, path); - - // TypeScript allows "." and ".." to implicitly refer to a tsconfig.json file. - if path == Path::new(".") || path == Path::new("..") { - absolute_path.push("tsconfig.json"); - } - - let mut exists = self.resolver.cache.fs.is_file(&absolute_path); - - // If the file doesn't exist, and doesn't end with `.json`, try appending the extension. - if !exists { - let try_extension = match absolute_path.extension() { - None => true, - Some(ext) => ext != "json", - }; - - if try_extension { - let mut os_str = absolute_path.into_os_string(); - os_str.push(".json"); - absolute_path = PathBuf::from(os_str); - exists = self.resolver.cache.fs.is_file(&absolute_path) - } - } - - if !exists { - return Err(ResolverError::TsConfigExtendsNotFound { - tsconfig: tsconfig.compiler_options.path.clone(), - error: Box::new(ResolverError::FileNotFound { - relative: path.to_path_buf(), - from: tsconfig.compiler_options.path.clone(), - }), - }); - } - - absolute_path - } - specifier @ Specifier::Package(..) => { - let resolver = Resolver { - project_root: Cow::Borrowed(&self.resolver.project_root), - extensions: Extensions::Borrowed(&["json"]), - index_file: "tsconfig.json", - entries: Fields::TSCONFIG, - flags: Flags::NODE_CJS, - cache: CacheCow::Borrowed(&self.resolver.cache), - include_node_modules: Cow::Owned(IncludeNodeModules::default()), - conditions: ExportsCondition::TYPES, - module_dir_resolver: self.resolver.module_dir_resolver.clone(), - }; - - let req = ResolveRequest::new( - &resolver, - specifier, - SpecifierType::Cjs, - &tsconfig.compiler_options.path, - self.invalidations, - ); - - let res = req - .resolve() - .map_err(|err| ResolverError::TsConfigExtendsNotFound { - tsconfig: tsconfig.compiler_options.path.clone(), - error: Box::new(err), - })?; - - if let Resolution::Path(res) = res { - res - } else { - return Err(ResolverError::TsConfigExtendsNotFound { - tsconfig: tsconfig.compiler_options.path.clone(), - error: Box::new(ResolverError::UnknownError), - }); - } - } - _ => return Ok(()), - }; - - let extended = self.read_tsconfig(path)?; - tsconfig.compiler_options.extend(extended); - } - - Ok(()) - }) - })?; - - Ok(&tsconfig.compiler_options) - } -} - -#[cfg(test)] -mod tests { - use std::collections::{HashMap, HashSet}; - - use super::*; - - fn root() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("node-resolver-core/test/fixture") - } - - fn test_resolver<'a>() -> Resolver<'a> { - Resolver::parcel( - root().into(), - CacheCow::Owned(Cache::new(Arc::new(OsFileSystem))), - ) - } - - fn node_resolver<'a>() -> Resolver<'a> { - Resolver::node( - root().into(), - CacheCow::Owned(Cache::new(Arc::new(OsFileSystem))), - ) - } - - #[test] - fn relative() { - assert_eq!( - test_resolver() - .resolve("./bar.js", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve(".///bar.js", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("./bar", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("~/bar", &root().join("nested/test.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("~bar", &root().join("nested/test.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "~/bar", - &root().join("node_modules/foo/nested/baz.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("./nested", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/index.js")) - ); - assert_eq!( - test_resolver() - .resolve("./bar?foo=2", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("./bar?foo=2", &root().join("foo.js"), SpecifierType::Cjs) - .result - .unwrap_err(), - ResolverError::FileNotFound { - relative: "bar?foo=2".into(), - from: root().join("foo.js") - }, - ); - assert_eq!( - test_resolver() - .resolve( - "./foo", - &root().join("priority/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("priority/foo.js")) - ); - - let invalidations = test_resolver() - .resolve("./bar", &root().join("foo.js"), SpecifierType::Esm) - .invalidations; - assert_eq!( - invalidations - .invalidate_on_file_create - .into_iter() - .collect::>(), - HashSet::new() - ); - assert_eq!( - invalidations - .invalidate_on_file_change - .into_iter() - .collect::>(), - HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) - ); - } - - #[test] - fn test_absolute() { - assert_eq!( - test_resolver() - .resolve("/bar", &root().join("nested/test.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "/bar", - &root().join("node_modules/foo/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - - #[cfg(not(windows))] - { - assert_eq!( - test_resolver() - .resolve( - "file:///bar", - &root().join("nested/test.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - node_resolver() - .resolve( - root().join("foo.js").to_str().unwrap(), - &root().join("nested/test.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("foo.js")) - ); - assert_eq!( - node_resolver() - .resolve( - &format!("file://{}", root().join("foo.js").to_str().unwrap()), - &root().join("nested/test.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("foo.js")) - ); - } - } - - #[test] - fn node_modules() { - assert_eq!( - test_resolver() - .resolve("foo", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - assert_eq!( - test_resolver() - .resolve("package-main", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-main/main.js")) - ); - assert_eq!( - test_resolver() - .resolve("package-module", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-module/module.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-browser", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-browser/browser.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-fallback", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-fallback/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-main-directory", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-main-directory/nested/index.js")) - ); - assert_eq!( - test_resolver() - .resolve("foo/nested/baz", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/nested/baz.js")) - ); - assert_eq!( - test_resolver() - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/@scope/pkg/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "@scope/pkg/foo/bar", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/@scope/pkg/foo/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo/with space.mjs", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/with space.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo/with%20space.mjs", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/with space.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo/with space.mjs", - &root().join("foo.js"), - SpecifierType::Cjs - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/with space.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo/with%20space.mjs", - &root().join("foo.js"), - SpecifierType::Cjs - ) - .result - .unwrap_err(), - ResolverError::ModuleSubpathNotFound { - module: "foo".into(), - path: root().join("node_modules/foo/with%20space.mjs"), - package_path: root().join("node_modules/foo/package.json") - }, - ); - assert_eq!( - test_resolver() - .resolve( - "@scope/pkg?foo=2", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/@scope/pkg/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "@scope/pkg?foo=2", - &root().join("foo.js"), - SpecifierType::Cjs - ) - .result - .unwrap_err(), - ResolverError::ModuleNotFound { - module: "@scope/pkg?foo=2".into() - }, - ); - - let invalidations = test_resolver() - .resolve("foo", &root().join("foo.js"), SpecifierType::Esm) - .invalidations; - assert_eq!( - invalidations - .invalidate_on_file_create - .into_iter() - .collect::>(), - HashSet::from([FileCreateInvalidation::FileName { - file_name: "node_modules/foo".into(), - above: root() - },]) - ); - assert_eq!( - invalidations - .invalidate_on_file_change - .into_iter() - .collect::>(), - HashSet::from([ - root().join("node_modules/foo/package.json"), - root().join("package.json"), - root().join("tsconfig.json") - ]) - ); - } - - #[test] - fn browser_field() { - assert_eq!( - test_resolver() - .resolve( - "package-browser-alias", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-browser-alias/browser.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-browser-alias/foo", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-browser-alias/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./foo", - &root().join("node_modules/package-browser-alias/browser.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-browser-alias/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./nested", - &root().join("node_modules/package-browser-alias/browser.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path( - root().join("node_modules/package-browser-alias/subfolder1/subfolder2/subfile.js") - ) - ); - } - - #[test] - fn local_aliases() { - assert_eq!( - test_resolver() - .resolve( - "package-alias/foo", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-alias/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./foo", - &root().join("node_modules/package-alias/browser.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-alias/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./lib/test", - &root().join("node_modules/package-alias-glob/browser.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-alias-glob/src/test.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-browser-exclude", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Empty - ); - assert_eq!( - test_resolver() - .resolve( - "./lib/test", - &root().join("node_modules/package-alias-glob/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-alias-glob/src/test.js")) - ); - - let invalidations = test_resolver() - .resolve( - "package-alias/foo", - &root().join("foo.js"), - SpecifierType::Esm, - ) - .invalidations; - assert_eq!( - invalidations - .invalidate_on_file_create - .into_iter() - .collect::>(), - HashSet::from([FileCreateInvalidation::FileName { - file_name: "node_modules/package-alias".into(), - above: root() - },]) - ); - assert_eq!( - invalidations - .invalidate_on_file_change - .into_iter() - .collect::>(), - HashSet::from([ - root().join("node_modules/package-alias/package.json"), - root().join("package.json"), - root().join("tsconfig.json") - ]) - ); - } - - #[test] - fn global_aliases() { - assert_eq!( - test_resolver() - .resolve("aliased", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliased", - &root().join("node_modules/package-alias/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliased/bar", - &root().join("node_modules/package-alias/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("aliased-file", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliased-file", - &root().join("node_modules/package-alias/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliasedfolder/test.js", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve("aliasedfolder", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/index.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliasedabsolute/test.js", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "aliasedabsolute", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/index.js")) - ); - assert_eq!( - test_resolver() - .resolve("foo/bar", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("glob/bar/test", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve("something", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "something", - &root().join("node_modules/package-alias/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-alias-exclude", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Empty - ); - assert_eq!( - test_resolver() - .resolve("./baz", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("../baz", &root().join("x/foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("~/baz", &root().join("x/foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./baz", - &root().join("node_modules/foo/bar.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/baz.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "~/baz", - &root().join("node_modules/foo/bar.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/baz.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "/baz", - &root().join("node_modules/foo/bar.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("url", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Empty - ); - } - - #[test] - fn test_urls() { - assert_eq!( - test_resolver() - .resolve( - "http://example.com/foo.png", - &root().join("foo.js"), - SpecifierType::Url - ) - .result - .unwrap() - .0, - Resolution::External - ); - assert_eq!( - test_resolver() - .resolve( - "//example.com/foo.png", - &root().join("foo.js"), - SpecifierType::Url - ) - .result - .unwrap() - .0, - Resolution::External - ); - assert_eq!( - test_resolver() - .resolve("#hash", &root().join("foo.js"), SpecifierType::Url) - .result - .unwrap() - .0, - Resolution::External - ); - assert_eq!( - test_resolver() - .resolve( - "http://example.com/foo.png", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::UnknownScheme { - scheme: "http".into() - }, - ); - assert_eq!( - test_resolver() - .resolve("bar.js", &root().join("foo.js"), SpecifierType::Url) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - // Reproduce bug for now - // assert_eq!( - // test_resolver() - // .resolve("bar", &root().join("foo.js"), SpecifierType::Url) - // .result - // .unwrap_err(), - // ResolverError::FileNotFound { - // relative: "bar".into(), - // from: root().join("foo.js") - // } - // ); - assert_eq!( - test_resolver() - .resolve("bar", &root().join("foo.js"), SpecifierType::Url) - .result - .unwrap() - .0, - Resolution::Path(root().join("bar.js")) - ); - assert_eq!( - test_resolver() - .resolve("npm:foo", &root().join("foo.js"), SpecifierType::Url) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - assert_eq!( - test_resolver() - .resolve("npm:@scope/pkg", &root().join("foo.js"), SpecifierType::Url) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/@scope/pkg/index.js")) - ); - } - - #[test] - fn test_exports() { - assert_eq!( - test_resolver() - .resolve( - "package-exports", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/main.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/foo", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - // "browser" field is NOT used. - Resolution::Path(root().join("node_modules/package-exports/foo.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/features/test", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/features/test.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/extensionless-features/test", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/features/test.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/extensionless-features/test.mjs", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/features/test.mjs")) - ); - assert_eq!( - node_resolver() - .resolve( - "package-exports/extensionless-features/test", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::ModuleSubpathNotFound { - module: "package-exports".into(), - package_path: root().join("node_modules/package-exports/package.json"), - path: root().join("node_modules/package-exports/features/test"), - }, - ); - assert_eq!( - node_resolver() - .resolve( - "package-exports/extensionless-features/test", - &root().join("foo.js"), - SpecifierType::Cjs - ) - .result - .unwrap_err(), - ResolverError::ModuleSubpathNotFound { - module: "package-exports".into(), - package_path: root().join("node_modules/package-exports/package.json"), - path: root().join("node_modules/package-exports/features/test"), - }, - ); - assert_eq!( - node_resolver() - .resolve( - "package-exports/extensionless-features/test.mjs", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/features/test.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/space", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/with space.mjs")) - ); - // assert_eq!( - // test_resolver().resolve("package-exports/with%20space", &root().join("foo.js"), SpecifierType::Esm).unwrap().0, - // Resolution::Path(root().join("node_modules/package-exports/with space.mjs")) - // ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/with space", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::PackageJsonError { - module: "package-exports".into(), - path: root().join("node_modules/package-exports/package.json"), - error: PackageJsonError::PackagePathNotExported - }, - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/internal", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::PackageJsonError { - module: "package-exports".into(), - path: root().join("node_modules/package-exports/package.json"), - error: PackageJsonError::PackagePathNotExported - }, - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/internal.mjs", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::PackageJsonError { - module: "package-exports".into(), - path: root().join("node_modules/package-exports/package.json"), - error: PackageJsonError::PackagePathNotExported - }, - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/invalid", - &root().join("foo.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::PackageJsonError { - module: "package-exports".into(), - path: root().join("node_modules/package-exports/package.json"), - error: PackageJsonError::InvalidPackageTarget - } - ); - } - - #[test] - fn test_self_reference() { - assert_eq!( - test_resolver() - .resolve( - "package-exports", - &root().join("node_modules/package-exports/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/main.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "package-exports/foo", - &root().join("node_modules/package-exports/foo.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/foo.mjs")) - ); - } - - #[test] - fn test_imports() { - assert_eq!( - test_resolver() - .resolve( - "#internal", - &root().join("node_modules/package-exports/main.mjs"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/package-exports/internal.mjs")) - ); - assert_eq!( - test_resolver() - .resolve( - "#foo", - &root().join("node_modules/package-exports/main.mjs"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - } - - #[test] - fn test_builtins() { - assert_eq!( - test_resolver() - .resolve("zlib", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Builtin("zlib".into()) - ); - assert_eq!( - test_resolver() - .resolve("node:zlib", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Builtin("zlib".into()) - ); - assert_eq!( - test_resolver() - .resolve( - "node:fs/promises", - &root().join("foo.js"), - SpecifierType::Cjs - ) - .result - .unwrap() - .0, - Resolution::Builtin("fs/promises".into()) - ); - } - - #[test] - fn test_tsconfig() { - assert_eq!( - test_resolver() - .resolve("ts-path", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("foo.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "ts-path", - &root().join("nested/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("nested/test.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo", - &root().join("tsconfig/index/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/tsconfig-index/foo.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo", - &root().join("tsconfig/field/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/tsconfig-field/foo.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo", - &root().join("tsconfig/exports/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/tsconfig-exports/foo.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "foo", - &root().join("tsconfig/extends-extension/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/extends-extension/foo.js")) - ); - - let mut extends_node_module_resolver = test_resolver(); - extends_node_module_resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Bool(false)); - assert_eq!( - extends_node_module_resolver - .resolve( - "./bar", - &root().join("tsconfig/extends-node-module/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/extends-node-module/bar.ts")) - ); - - assert_eq!( - test_resolver() - .resolve( - "ts-path", - &root().join("node_modules/tsconfig-not-used/index.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::ModuleNotFound { - module: "ts-path".into() - }, - ); - assert_eq!( - test_resolver() - .resolve("ts-path", &root().join("foo.css"), SpecifierType::Esm) - .result - .unwrap_err(), - ResolverError::ModuleNotFound { - module: "ts-path".into() - }, - ); - assert_eq!( - test_resolver() - .resolve( - "zlib", - &root().join("tsconfig/builtins/thing.js"), - SpecifierType::Cjs - ) - .result - .unwrap() - .0, - Resolution::Builtin("zlib".into()) - ); - - let invalidations = test_resolver() - .resolve("ts-path", &root().join("foo.js"), SpecifierType::Esm) - .invalidations; - assert_eq!( - invalidations - .invalidate_on_file_create - .into_iter() - .collect::>(), - HashSet::new() - ); - assert_eq!( - invalidations - .invalidate_on_file_change - .into_iter() - .collect::>(), - HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) - ); - } - - #[test] - fn test_module_suffixes() { - assert_eq!( - test_resolver() - .resolve( - "./a", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/a.ios.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./a.ts", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/a.ios.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./b", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/b.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./b.ts", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/b.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./c", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/c-test.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./c.ts", - &root().join("tsconfig/suffixes/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/suffixes/c-test.ts")) - ); - } - - #[test] - fn test_tsconfig_parsing() { - assert_eq!( - test_resolver() - .resolve( - "foo", - &root().join("tsconfig/trailing-comma/index.js"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("tsconfig/trailing-comma/bar.js")) - ); - } - - #[test] - fn test_ts_extensions() { - assert_eq!( - test_resolver() - .resolve( - "./a.js", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("ts-extensions/a.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./a.jsx", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - // TSC always prioritizes .ts over .tsx - Resolution::Path(root().join("ts-extensions/a.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./a.mjs", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("ts-extensions/a.mts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./a.cjs", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - Resolution::Path(root().join("ts-extensions/a.cts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./b.js", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - // We deviate from TSC here to match Node/bundlers. - Resolution::Path(root().join("ts-extensions/b.js")) - ); - assert_eq!( - test_resolver() - .resolve( - "./c.js", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm - ) - .result - .unwrap() - .0, - // This matches TSC. c.js.ts seems kinda unlikely? - Resolution::Path(root().join("ts-extensions/c.ts")) - ); - assert_eq!( - test_resolver() - .resolve( - "./a.js", - &root().join("ts-extensions/index.js"), - SpecifierType::Esm - ) - .result - .unwrap_err(), - ResolverError::FileNotFound { - relative: "a.js".into(), - from: root().join("ts-extensions/index.js") - }, - ); - - let invalidations = test_resolver() - .resolve( - "./a.js", - &root().join("ts-extensions/index.ts"), - SpecifierType::Esm, - ) - .invalidations; - assert_eq!( - invalidations - .invalidate_on_file_create - .into_iter() - .collect::>(), - HashSet::from([ - FileCreateInvalidation::Path(root().join("ts-extensions/a.js")), - FileCreateInvalidation::FileName { - file_name: "package.json".into(), - above: root().join("ts-extensions") - }, - FileCreateInvalidation::FileName { - file_name: "tsconfig.json".into(), - above: root().join("ts-extensions") - }, - ]) - ); - assert_eq!( - invalidations - .invalidate_on_file_change - .into_iter() - .collect::>(), - HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) - ); - } - - fn resolve_side_effects(specifier: &str, from: &Path) -> bool { - let resolver = test_resolver(); - let resolved = resolver - .resolve(specifier, from, SpecifierType::Esm) - .result - .unwrap() - .0; - - if let Resolution::Path(path) = resolved { - resolver - .resolve_side_effects(&path, &Invalidations::default()) - .unwrap() - } else { - unreachable!() - } - } - - #[test] - fn test_side_effects() { - assert!(!resolve_side_effects( - "side-effects-false/src/index.js", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-false/src/index", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-false/src/", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-false", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-package-redirect-up/foo/bar", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-package-redirect-down/foo/bar", - &root().join("foo.js") - )); - assert!(resolve_side_effects( - "side-effects-false-glob/a/index", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-false-glob/b/index.js", - &root().join("foo.js") - )); - assert!(!resolve_side_effects( - "side-effects-false-glob/sub/a/index.js", - &root().join("foo.js") - )); - assert!(resolve_side_effects( - "side-effects-false-glob/sub/index.json", - &root().join("foo.js") - )); - } - - #[test] - fn test_include_node_modules() { - let mut resolver = test_resolver(); - resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Bool(false)); - - assert_eq!( - resolver - .resolve("foo", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::External - ); - assert_eq!( - resolver - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::External - ); - - resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Array(vec!["foo".into()])); - assert_eq!( - resolver - .resolve("foo", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/foo/index.js")) - ); - assert_eq!( - resolver - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::External - ); - - resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Map(HashMap::from([ - ("foo".into(), false), - ("@scope/pkg".into(), true), - ]))); - assert_eq!( - resolver - .resolve("foo", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::External - ); - assert_eq!( - resolver - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap() - .0, - Resolution::Path(root().join("node_modules/@scope/pkg/index.js")) - ); - } - - // #[test] - // fn test_visitor() { - // let resolved = test_resolver().resolve("unified", &root(), SpecifierType::Esm).unwrap(); - // println!("{:?}", resolved); - // if let Resolution::Path(p) = resolved { - // let res = build_esm_graph( - // &p, - // root() - // ).unwrap(); - // println!("{:?}", res); - // } - // } -} diff --git a/packages/utils/node-resolver-rs-old/src/package_json.rs b/packages/utils/node-resolver-rs-old/src/package_json.rs deleted file mode 100644 index 58bea5810b5..00000000000 --- a/packages/utils/node-resolver-rs-old/src/package_json.rs +++ /dev/null @@ -1,1611 +0,0 @@ -use std::borrow::Cow; -use std::cmp::Ordering; -use std::ops::Range; -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -use bitflags::bitflags; -use glob_match::glob_match; -use glob_match::glob_match_with_captures; -use indexmap::IndexMap; -use serde::Deserialize; - -pub use parcel_core::types::ExportsCondition; - -use crate::path::resolve_path; -use crate::specifier::decode_path; -use crate::specifier::Specifier; -use crate::specifier::SpecifierType; - -bitflags! { - #[derive(serde::Serialize)] - pub struct Fields: u8 { - const MAIN = 1 << 0; - const MODULE = 1 << 1; - const SOURCE = 1 << 2; - const BROWSER = 1 << 3; - const ALIAS = 1 << 4; - const TSCONFIG = 1 << 5; - const TYPES = 1 << 6; - } -} - -#[derive(serde::Deserialize, Debug, Default)] -#[serde(rename_all = "camelCase")] -pub struct PackageJson<'a> { - #[serde(skip)] - pub path: PathBuf, - #[serde(default)] - pub name: &'a str, - #[serde(rename = "type", default)] - pub module_type: ModuleType, - main: Option<&'a str>, - module: Option<&'a str>, - tsconfig: Option<&'a str>, - types: Option<&'a str>, - #[serde(default)] - pub source: SourceField<'a>, - #[serde(default)] - browser: BrowserField<'a>, - #[serde(default)] - alias: IndexMap, AliasValue<'a>>, - #[serde(default)] - exports: ExportsField<'a>, - #[serde(default)] - imports: IndexMap, ExportsField<'a>>, - #[serde(default)] - side_effects: SideEffects<'a>, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Default, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum ModuleType { - Module, - Json, - #[default] - #[serde(other)] - CommonJs, -} - -#[derive(serde::Deserialize, Debug, Default)] -#[serde(untagged)] -pub enum BrowserField<'a> { - #[default] - None, - #[serde(borrow)] - String(&'a str), - Map(IndexMap, AliasValue<'a>>), -} - -#[derive(serde::Deserialize, Debug, Default)] -#[serde(untagged)] -pub enum SourceField<'a> { - #[default] - None, - #[serde(borrow)] - String(&'a str), - Map(IndexMap, AliasValue<'a>>), - Array(Vec<&'a str>), - Bool(bool), -} - -#[derive(serde::Deserialize, Debug, Default, PartialEq)] -#[serde(untagged)] -pub enum ExportsField<'a> { - #[default] - None, - #[serde(borrow)] - String(&'a str), - Array(Vec>), - Map(IndexMap, ExportsField<'a>>), -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub enum ExportsKey<'a> { - Main, - Pattern(&'a str), - Condition(ExportsCondition), - CustomCondition(&'a str), -} - -impl<'a> From<&'a str> for ExportsKey<'a> { - fn from(key: &'a str) -> Self { - if key == "." { - ExportsKey::Main - } else if let Some(key) = key.strip_prefix("./") { - ExportsKey::Pattern(key) - } else if let Some(key) = key.strip_prefix('#') { - ExportsKey::Pattern(key) - } else if let Ok(c) = ExportsCondition::try_from(key) { - ExportsKey::Condition(c) - } else { - ExportsKey::CustomCondition(key) - } - } -} - -impl<'a, 'de: 'a> Deserialize<'de> for ExportsKey<'a> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &'de str = Deserialize::deserialize(deserializer)?; - Ok(ExportsKey::from(s)) - } -} - -#[derive(serde::Deserialize, Clone, PartialEq, Debug)] -#[serde(untagged)] -pub enum AliasValue<'a> { - #[serde(borrow)] - Specifier(Specifier<'a>), - Bool(bool), - Global { - global: &'a str, - }, -} - -#[derive(serde::Deserialize, Clone, Default, PartialEq, Debug)] -#[serde(untagged)] -pub enum SideEffects<'a> { - #[default] - None, - Boolean(bool), - #[serde(borrow)] - String(&'a str), - Array(Vec<&'a str>), -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize)] -pub enum PackageJsonError { - InvalidPackageTarget, - PackagePathNotExported, - InvalidSpecifier, - ImportNotDefined, -} - -#[derive(Debug, PartialEq)] -pub enum ExportsResolution<'a> { - None, - Path(PathBuf), - Package(Cow<'a, str>), -} - -impl<'a> PackageJson<'a> { - pub fn parse(path: PathBuf, data: &'a str) -> serde_json::Result> { - let mut parsed: PackageJson = serde_json::from_str(data)?; - parsed.path = path; - Ok(parsed) - } - - pub fn entries(&self, fields: Fields) -> EntryIter { - EntryIter { - package: self, - fields, - } - } - - pub fn source(&self) -> Option { - match &self.source { - SourceField::None | SourceField::Array(_) | SourceField::Bool(_) => None, - SourceField::String(source) => Some(resolve_path(&self.path, source)), - SourceField::Map(map) => match map.get(&Specifier::Package( - Cow::Borrowed(self.name), - Cow::Borrowed(""), - )) { - Some(AliasValue::Specifier(Specifier::Relative(s))) => Some(resolve_path(&self.path, s)), - _ => None, - }, - } - } - - pub fn has_exports(&self) -> bool { - self.exports != ExportsField::None - } - - pub fn resolve_package_exports( - &self, - subpath: &'a str, - conditions: ExportsCondition, - custom_conditions: &[String], - ) -> Result { - // If exports is an Object with both a key starting with "." and a key not starting with ".", throw an Invalid Package Configuration error. - if let ExportsField::Map(map) = &self.exports { - let mut has_conditions = false; - let mut has_patterns = false; - for key in map.keys() { - has_conditions = has_conditions - || matches!( - key, - ExportsKey::Condition(..) | ExportsKey::CustomCondition(..) - ); - has_patterns = has_patterns || matches!(key, ExportsKey::Pattern(..) | ExportsKey::Main); - if has_conditions && has_patterns { - return Err(PackageJsonError::InvalidPackageTarget); - } - } - } - - if subpath.is_empty() { - let mut main_export = &ExportsField::None; - match &self.exports { - ExportsField::None | ExportsField::String(_) | ExportsField::Array(_) => { - main_export = &self.exports; - } - ExportsField::Map(map) => { - if let Some(v) = map.get(&ExportsKey::Main) { - main_export = v; - } else if !map.keys().any(|k| matches!(k, ExportsKey::Pattern(_))) { - main_export = &self.exports; - } - } - } - - if main_export != &ExportsField::None { - match self.resolve_package_target(main_export, "", false, conditions, custom_conditions)? { - ExportsResolution::Path(path) => return Ok(path), - ExportsResolution::None | ExportsResolution::Package(..) => {} - } - } - } else if let ExportsField::Map(exports) = &self.exports { - // All exports must start with "." at this point. - match self.resolve_package_imports_exports( - subpath, - exports, - false, - conditions, - custom_conditions, - )? { - ExportsResolution::Path(path) => return Ok(path), - ExportsResolution::None | ExportsResolution::Package(..) => {} - } - } - - Err(PackageJsonError::PackagePathNotExported) - } - - pub fn resolve_package_imports( - &self, - specifier: &'a str, - conditions: ExportsCondition, - custom_conditions: &[String], - ) -> Result, PackageJsonError> { - if specifier == "#" || specifier.starts_with("#/") { - return Err(PackageJsonError::InvalidSpecifier); - } - - match self.resolve_package_imports_exports( - specifier, - &self.imports, - true, - conditions, - custom_conditions, - )? { - ExportsResolution::None => {} - res => return Ok(res), - } - - Err(PackageJsonError::ImportNotDefined) - } - - fn resolve_package_target( - &self, - target: &'a ExportsField, - pattern_match: &str, - is_imports: bool, - conditions: ExportsCondition, - custom_conditions: &[String], - ) -> Result, PackageJsonError> { - match target { - ExportsField::String(target) => { - if !target.starts_with("./") { - if !is_imports || target.starts_with("../") || target.starts_with('/') { - return Err(PackageJsonError::InvalidPackageTarget); - } - - if !pattern_match.is_empty() { - let target = target.replace('*', pattern_match); - return Ok(ExportsResolution::Package(Cow::Owned(target))); - } - - return Ok(ExportsResolution::Package(Cow::Borrowed(target))); - } - - let target = if pattern_match.is_empty() { - Cow::Borrowed(*target) - } else { - Cow::Owned(target.replace('*', pattern_match)) - }; - - // If target split on "/" or "\" contains any "", ".", "..", or "node_modules" segments after - // the first "." segment, case insensitive and including percent encoded variants, - // throw an Invalid Package Target error. - let target_path = decode_path(target.as_ref(), SpecifierType::Esm).0; - if target_path - .components() - .enumerate() - .any(|(index, c)| match c { - Component::ParentDir => true, - Component::CurDir => index > 0, - Component::Normal(c) => c.eq_ignore_ascii_case("node_modules"), - _ => false, - }) - { - return Err(PackageJsonError::InvalidPackageTarget); - } - - let resolved_target = resolve_path(&self.path, &target_path); - return Ok(ExportsResolution::Path(resolved_target)); - } - ExportsField::Map(target) => { - // We must iterate in object insertion order. - for (key, value) in target { - let matches = match key { - ExportsKey::Condition(key) => { - *key == ExportsCondition::DEFAULT || conditions.contains(*key) - } - ExportsKey::CustomCondition(key) => custom_conditions.iter().any(|k| k == key), - _ => false, - }; - if matches { - match self.resolve_package_target( - value, - pattern_match, - is_imports, - conditions, - custom_conditions, - )? { - ExportsResolution::None => continue, - res => return Ok(res), - } - } - } - } - ExportsField::Array(target) => { - if target.is_empty() { - return Err(PackageJsonError::PackagePathNotExported); - } - - for item in target { - match self.resolve_package_target( - item, - pattern_match, - is_imports, - conditions, - custom_conditions, - ) { - Err(_) | Ok(ExportsResolution::None) => continue, - Ok(res) => return Ok(res), - } - } - } - ExportsField::None => return Ok(ExportsResolution::None), - } - - Ok(ExportsResolution::None) - } - - fn resolve_package_imports_exports( - &self, - match_key: &'a str, - match_obj: &'a IndexMap, ExportsField<'a>>, - is_imports: bool, - conditions: ExportsCondition, - custom_conditions: &[String], - ) -> Result, PackageJsonError> { - let pattern = ExportsKey::Pattern(match_key); - if let Some(target) = match_obj.get(&pattern) { - if !match_key.contains('*') { - return self.resolve_package_target(target, "", is_imports, conditions, custom_conditions); - } - } - - let mut best_key = ""; - let mut best_match = ""; - for key in match_obj.keys() { - if let ExportsKey::Pattern(key) = key { - if let Some((pattern_base, pattern_trailer)) = key.split_once('*') { - if match_key.starts_with(pattern_base) - && !pattern_trailer.contains('*') - && (pattern_trailer.is_empty() - || (match_key.len() >= key.len() && match_key.ends_with(pattern_trailer))) - && pattern_key_compare(best_key, key) == Ordering::Greater - { - best_key = key; - best_match = &match_key[pattern_base.len()..match_key.len() - pattern_trailer.len()]; - } - } - } - } - - if !best_key.is_empty() { - return self.resolve_package_target( - &match_obj[&ExportsKey::Pattern(best_key)], - best_match, - is_imports, - conditions, - custom_conditions, - ); - } - - Ok(ExportsResolution::None) - } - - pub fn resolve_aliases( - &self, - specifier: &Specifier<'a>, - fields: Fields, - ) -> Option> { - if fields.contains(Fields::SOURCE) { - if let SourceField::Map(source) = &self.source { - match self.resolve_alias(source, specifier) { - None => {} - res => return res, - } - } - } - - if fields.contains(Fields::ALIAS) { - match self.resolve_alias(&self.alias, specifier) { - None => {} - res => return res, - } - } - - if fields.contains(Fields::BROWSER) { - if let BrowserField::Map(browser) = &self.browser { - match self.resolve_alias(browser, specifier) { - None => {} - res => return res, - } - } - } - - None - } - - fn resolve_alias( - &self, - map: &'a IndexMap, AliasValue<'a>>, - specifier: &Specifier<'a>, - ) -> Option> { - if let Some(alias) = self.lookup_alias(map, specifier) { - return Some(alias); - } - - if let Specifier::Package(package, subpath) = specifier { - if let Some(alias) = - self.lookup_alias(map, &Specifier::Package(package.clone(), Cow::Borrowed(""))) - { - match alias.as_ref() { - AliasValue::Specifier(base) => { - // Join the subpath back onto the resolved alias. - match base { - Specifier::Package(base_pkg, base_subpath) => { - let subpath = if !base_subpath.is_empty() && !subpath.is_empty() { - Cow::Owned(format!("{}/{}", base_subpath, subpath)) - } else if !subpath.is_empty() { - subpath.clone() - } else { - return Some(alias); - }; - return Some(Cow::Owned(AliasValue::Specifier(Specifier::Package( - base_pkg.clone(), - subpath, - )))); - } - Specifier::Relative(path) => { - if subpath.is_empty() { - return Some(alias); - } else { - return Some(Cow::Owned(AliasValue::Specifier(Specifier::Relative( - Cow::Owned(path.join(subpath.as_ref())), - )))); - } - } - Specifier::Absolute(path) => { - if subpath.is_empty() { - return Some(alias); - } else { - return Some(Cow::Owned(AliasValue::Specifier(Specifier::Absolute( - Cow::Owned(path.join(subpath.as_ref())), - )))); - } - } - Specifier::Tilde(path) => { - if subpath.is_empty() { - return Some(alias); - } else { - return Some(Cow::Owned(AliasValue::Specifier(Specifier::Tilde( - Cow::Owned(path.join(subpath.as_ref())), - )))); - } - } - _ => return Some(alias), - } - } - _ => return Some(alias), - }; - } - } - - None - } - - fn lookup_alias( - &self, - map: &'a IndexMap, AliasValue<'a>>, - specifier: &Specifier<'a>, - ) -> Option> { - if let Some(value) = map.get(specifier) { - return Some(Cow::Borrowed(value)); - } - - // Match glob aliases. - for (key, value) in map { - let (glob, path) = match (key, specifier) { - (Specifier::Relative(glob), Specifier::Relative(path)) - | (Specifier::Absolute(glob), Specifier::Absolute(path)) - | (Specifier::Tilde(glob), Specifier::Tilde(path)) => ( - glob.as_os_str().to_string_lossy(), - path.as_os_str().to_string_lossy(), - ), - (Specifier::Package(module_a, glob), Specifier::Package(module_b, path)) - if module_a == module_b => - { - (Cow::Borrowed(glob.as_ref()), Cow::Borrowed(path.as_ref())) - } - (pkg_a @ Specifier::Package(..), pkg_b @ Specifier::Package(..)) => { - // Glob could be in the package name, e.g. "@internal/*" - (pkg_a.to_string(), pkg_b.to_string()) - } - _ => continue, - }; - - if let Some(captures) = glob_match_with_captures(&glob, &path) { - let res = match value { - AliasValue::Specifier(specifier) => AliasValue::Specifier(match specifier { - Specifier::Relative(r) => { - Specifier::Relative(replace_path_captures(r, &path, &captures)?) - } - Specifier::Absolute(r) => { - Specifier::Absolute(replace_path_captures(r, &path, &captures)?) - } - Specifier::Tilde(r) => Specifier::Tilde(replace_path_captures(r, &path, &captures)?), - Specifier::Package(module, subpath) => { - Specifier::Package(module.clone(), replace_captures(subpath, &path, &captures)) - } - _ => return Some(Cow::Borrowed(value)), - }), - _ => return Some(Cow::Borrowed(value)), - }; - - return Some(Cow::Owned(res)); - } - } - - None - } - - pub fn has_side_effects(&self, path: &Path) -> bool { - let path = path - .strip_prefix(self.path.parent().unwrap()) - .ok() - .and_then(|path| path.as_os_str().to_str()); - - let path = match path { - Some(p) => p, - None => return true, - }; - - fn side_effects_glob_matches(glob: &str, path: &str) -> bool { - // Trim leading "./" - let glob = glob.strip_prefix("./").unwrap_or(glob); - - // If the glob does not contain any '/' characters, prefix with "**/" to match webpack. - let glob = if !glob.contains('/') { - Cow::Owned(format!("**/{}", glob)) - } else { - Cow::Borrowed(glob) - }; - - glob_match(glob.as_ref(), path) - } - - match &self.side_effects { - SideEffects::None => true, - SideEffects::Boolean(b) => *b, - SideEffects::String(glob) => side_effects_glob_matches(glob, path), - SideEffects::Array(globs) => globs - .iter() - .any(|glob| side_effects_glob_matches(glob, path)), - } - } -} - -fn replace_path_captures<'a>( - s: &'a Path, - path: &str, - captures: &Vec>, -) -> Option> { - Some( - match replace_captures(s.as_os_str().to_str()?, path, captures) { - Cow::Borrowed(b) => Cow::Borrowed(Path::new(b)), - Cow::Owned(b) => Cow::Owned(PathBuf::from(b)), - }, - ) -} - -/// Inserts captures matched in a glob against `path` using a pattern string. -/// Replacements are inserted using JS-like $N syntax, e.g. $1 for the first capture. -fn replace_captures<'a>(s: &'a str, path: &str, captures: &Vec>) -> Cow<'a, str> { - let mut res = Cow::Borrowed(s); - let bytes = s.as_bytes(); - for (idx, _) in s.match_indices('$').rev() { - let mut end = idx; - while end + 1 < bytes.len() && bytes[end + 1].is_ascii_digit() { - end += 1; - } - - if end != idx { - if let Ok(capture_index) = s[idx + 1..end + 1].parse::() { - if capture_index > 0 && capture_index - 1 < captures.len() { - res - .to_mut() - .replace_range(idx..end + 1, &path[captures[capture_index - 1].clone()]); - } - } - } - } - - res -} - -fn pattern_key_compare(a: &str, b: &str) -> Ordering { - let a_pos = a.chars().position(|c| c == '*'); - let b_pos = b.chars().position(|c| c == '*'); - let base_length_a = a_pos.map_or(a.len(), |p| p + 1); - let base_length_b = b_pos.map_or(b.len(), |p| p + 1); - let cmp = base_length_b.cmp(&base_length_a); - if cmp != Ordering::Equal { - return cmp; - } - - if a_pos.is_none() { - return Ordering::Greater; - } - - if b_pos.is_none() { - return Ordering::Less; - } - - b.len().cmp(&a.len()) -} - -pub struct EntryIter<'a> { - package: &'a PackageJson<'a>, - fields: Fields, -} - -impl<'a> Iterator for EntryIter<'a> { - type Item = (PathBuf, &'static str); - - fn next(&mut self) -> Option { - if self.fields.contains(Fields::SOURCE) { - self.fields.remove(Fields::SOURCE); - if let Some(source) = self.package.source() { - return Some((source, "source")); - } - } - - if self.fields.contains(Fields::TYPES) { - self.fields.remove(Fields::TYPES); - if let Some(types) = self.package.types { - return Some((resolve_path(&self.package.path, types), "types")); - } - } - - if self.fields.contains(Fields::BROWSER) { - self.fields.remove(Fields::BROWSER); - match &self.package.browser { - BrowserField::None => {} - BrowserField::String(browser) => { - return Some((resolve_path(&self.package.path, browser), "browser")) - } - BrowserField::Map(map) => { - if let Some(AliasValue::Specifier(Specifier::Relative(s))) = map.get(&Specifier::Package( - Cow::Borrowed(self.package.name), - Cow::Borrowed(""), - )) { - return Some((resolve_path(&self.package.path, s), "browser")); - } - } - } - } - - if self.fields.contains(Fields::MODULE) { - self.fields.remove(Fields::MODULE); - if let Some(module) = self.package.module { - return Some((resolve_path(&self.package.path, module), "module")); - } - } - - if self.fields.contains(Fields::MAIN) { - self.fields.remove(Fields::MAIN); - if let Some(main) = self.package.main { - return Some((resolve_path(&self.package.path, main), "main")); - } - } - - if self.fields.contains(Fields::TSCONFIG) { - self.fields.remove(Fields::TSCONFIG); - if let Some(tsconfig) = self.package.tsconfig { - return Some((resolve_path(&self.package.path, tsconfig), "tsconfig")); - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use indexmap::indexmap; - - use super::*; - - // Based on https://github.com/lukeed/resolve.exports/blob/master/test/resolve.js, - // https://github.com/privatenumber/resolve-pkg-maps/tree/develop/tests, and - // https://github.com/webpack/enhanced-resolve/blob/main/test/exportsField.js - - #[test] - fn exports_string() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::String("./exports.js"), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/exports.js") - ); - // assert_eq!(pkg.resolve_package_exports("./exports.js", &[]).unwrap(), PathBuf::from("/foo/exports.js")); - // assert_eq!(pkg.resolve_package_exports("foobar", &[]).unwrap(), PathBuf::from("/foo/exports.js")); - } - - #[test] - fn exports_dot() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - ".".into() => ExportsField::String("./exports.js") - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/exports.js") - ); - assert!(matches!( - pkg.resolve_package_exports(".", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - // assert_eq!(pkg.resolve_package_exports("foobar", &[]).unwrap(), PathBuf::from("/foo/exports.js")); - } - - #[test] - fn exports_dot_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - ".".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String("./import.js"), - "require".into() => ExportsField::String("./require.js") - }) - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports( - "", - ExportsCondition::IMPORT | ExportsCondition::REQUIRE, - &[] - ) - .unwrap(), - PathBuf::from("/foo/import.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::REQUIRE, &[]) - .unwrap(), - PathBuf::from("/foo/require.js") - ); - assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - } - - #[test] - fn exports_map_string() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./foo".into() => ExportsField::String("./exports.js"), - "./.invisible".into() => ExportsField::String("./.invisible.js"), - "./".into() => ExportsField::String("./"), - "./*".into() => ExportsField::String("./*.js") - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("foo", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/exports.js") - ); - assert_eq!( - pkg - .resolve_package_exports(".invisible", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/.invisible.js") - ); - assert_eq!( - pkg - .resolve_package_exports("file", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/file.js") - ); - } - - #[test] - fn exports_map_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./foo".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String("./import.js"), - "require".into() => ExportsField::String("./require.js") - }) - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports( - "foo", - ExportsCondition::IMPORT | ExportsCondition::REQUIRE, - &[] - ) - .unwrap(), - PathBuf::from("/foo/import.js") - ); - assert_eq!( - pkg - .resolve_package_exports("foo", ExportsCondition::REQUIRE, &[]) - .unwrap(), - PathBuf::from("/foo/require.js") - ); - assert!(matches!( - pkg.resolve_package_exports("foo", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - assert!(matches!( - pkg.resolve_package_exports("foo", ExportsCondition::NODE, &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - } - - #[test] - fn nested_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "node".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String("./import.js"), - "require".into() => ExportsField::String("./require.js") - }), - "default".into() => ExportsField::String("./default.js") - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::IMPORT, &[]) - .unwrap(), - PathBuf::from("/foo/import.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::REQUIRE, &[]) - .unwrap(), - PathBuf::from("/foo/require.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::IMPORT, &[]) - .unwrap(), - PathBuf::from("/foo/default.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/default.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE, &[]) - .unwrap(), - PathBuf::from("/foo/default.js") - ); - } - - #[test] - fn custom_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "custom".into() => ExportsField::String("./custom.js"), - "default".into() => ExportsField::String("./default.js") - }), - ..PackageJson::default() - }; - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE, &["custom".into()]) - .unwrap(), - PathBuf::from("/foo/custom.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE, &[]) - .unwrap(), - PathBuf::from("/foo/default.js") - ); - } - - #[test] - fn subpath_nested_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./lite".into() => ExportsField::Map(indexmap! { - "node".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String("./node_import.js"), - "require".into() => ExportsField::String("./node_require.js") - }), - "browser".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String("./browser_import.js"), - "require".into() => ExportsField::String("./browser_require.js") - }), - }) - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports( - "lite", - ExportsCondition::NODE | ExportsCondition::IMPORT, - &[] - ) - .unwrap(), - PathBuf::from("/foo/node_import.js") - ); - assert_eq!( - pkg - .resolve_package_exports( - "lite", - ExportsCondition::NODE | ExportsCondition::REQUIRE, - &[] - ) - .unwrap(), - PathBuf::from("/foo/node_require.js") - ); - assert_eq!( - pkg - .resolve_package_exports( - "lite", - ExportsCondition::BROWSER | ExportsCondition::IMPORT, - &[] - ) - .unwrap(), - PathBuf::from("/foo/browser_import.js") - ); - assert_eq!( - pkg - .resolve_package_exports( - "lite", - ExportsCondition::BROWSER | ExportsCondition::REQUIRE, - &[] - ) - .unwrap(), - PathBuf::from("/foo/browser_require.js") - ); - assert!(matches!( - pkg.resolve_package_exports("lite", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - } - - #[test] - fn subpath_star() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./*".into() => ExportsField::String("./cheese/*.mjs"), - "./pizza/*".into() => ExportsField::String("./pizza/*.mjs"), - "./burritos/*".into() => ExportsField::String("./burritos/*/*.mjs"), - "./literal".into() => ExportsField::String("./literal/*.js"), - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("hello", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/cheese/hello.mjs") - ); - assert_eq!( - pkg - .resolve_package_exports("hello/world", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/cheese/hello/world.mjs") - ); - assert_eq!( - pkg - .resolve_package_exports("hello.js", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/cheese/hello.js.mjs") - ); - assert_eq!( - pkg - .resolve_package_exports("pizza/test", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/pizza/test.mjs") - ); - assert_eq!( - pkg - .resolve_package_exports("burritos/test", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/burritos/test/test.mjs") - ); - assert_eq!( - pkg - .resolve_package_exports("literal", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/literal/*.js") - ); - - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./*".into() => ExportsField::String("./*.js"), - "./*.js".into() => ExportsField::None, - "./internal/*".into() => ExportsField::None, - }), - ..PackageJson::default() - }; - assert_eq!( - pkg - .resolve_package_exports("file", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/file.js") - ); - assert!(matches!( - pkg.resolve_package_exports("file.js", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - assert!(matches!( - pkg.resolve_package_exports("internal/file", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - } - - #[test] - fn exports_null() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./features/*.js".into() => ExportsField::String("./src/features/*.js"), - "./features/private-internal/*".into() => ExportsField::None, - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("features/foo.js", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/src/features/foo.js") - ); - assert_eq!( - pkg - .resolve_package_exports("features/foo/bar.js", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/src/features/foo/bar.js") - ); - assert!(matches!( - pkg.resolve_package_exports( - "features/private-internal/foo.js", - ExportsCondition::empty(), - &[] - ), - Err(PackageJsonError::PackagePathNotExported) - ),); - } - - #[test] - fn exports_array() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./utils/*".into() => ExportsField::Map(indexmap! { - "browser".into() => ExportsField::Map(indexmap! { - "worklet".into() => ExportsField::Array(vec![ExportsField::String("./*"), ExportsField::String("./node/*")]), - "default".into() => ExportsField::Map(indexmap! { - "node".into() => ExportsField::String("./node/*") - }) - }) - }), - "./test/*".into() => ExportsField::Array(vec![ExportsField::String("lodash/*"), ExportsField::String("./bar/*")]), - "./file".into() => ExportsField::Array(vec![ExportsField::String("http://a.com"), ExportsField::String("./file.js")]) - }), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports( - "utils/index.js", - ExportsCondition::BROWSER | ExportsCondition::WORKLET, - &[] - ) - .unwrap(), - PathBuf::from("/foo/index.js") - ); - assert_eq!( - pkg - .resolve_package_exports( - "utils/index.js", - ExportsCondition::BROWSER | ExportsCondition::NODE, - &[] - ) - .unwrap(), - PathBuf::from("/foo/node/index.js") - ); - assert_eq!( - pkg - .resolve_package_exports("test/index.js", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/bar/index.js") - ); - assert_eq!( - pkg - .resolve_package_exports("file", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/file.js") - ); - assert!(matches!( - pkg.resolve_package_exports("utils/index.js", ExportsCondition::BROWSER, &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - assert!(matches!( - pkg.resolve_package_exports("dir/file.js", ExportsCondition::BROWSER, &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Array(vec![ - ExportsField::Map(indexmap! { - "node".into() => ExportsField::String("./a.js") - }), - ExportsField::String("./b.js"), - ]), - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::empty(), &[]) - .unwrap(), - PathBuf::from("/foo/b.js") - ); - assert_eq!( - pkg - .resolve_package_exports("", ExportsCondition::NODE, &[]) - .unwrap(), - PathBuf::from("/foo/a.js") - ); - } - - #[test] - fn exports_invalid() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - "./invalid".into() => ExportsField::String("../invalid"), - "./absolute".into() => ExportsField::String("/absolute"), - "./package".into() => ExportsField::String("package"), - "./utils/index".into() => ExportsField::String("./src/../index.js"), - "./dist/*".into() => ExportsField::String("./src/../../*"), - "./modules/*".into() => ExportsField::String("./node_modules/*"), - "./modules2/*".into() => ExportsField::String("./NODE_MODULES/*"), - "./*/*".into() => ExportsField::String("./file.js") - }), - ..PackageJson::default() - }; - - assert!(matches!( - pkg.resolve_package_exports("invalid", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("absolute", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("package", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("utils/index", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("dist/foo", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("modules/foo", ExportsCondition::empty(), &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("a/b", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - assert!(matches!( - pkg.resolve_package_exports("a/*", ExportsCondition::empty(), &[]), - Err(PackageJsonError::PackagePathNotExported) - )); - - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - exports: ExportsField::Map(indexmap! { - ".".into() => ExportsField::String("./foo.js"), - "node".into() => ExportsField::String("./bar.js"), - }), - ..PackageJson::default() - }; - - assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), - Err(PackageJsonError::InvalidPackageTarget) - )); - } - - #[test] - fn imports() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - imports: indexmap! { - "#foo".into() => ExportsField::String("./foo.mjs"), - "#internal/*".into() => ExportsField::String("./src/internal/*.mjs"), - "#bar".into() => ExportsField::String("bar"), - }, - ..PackageJson::default() - }; - - assert_eq!( - pkg - .resolve_package_imports("foo", ExportsCondition::empty(), &[]) - .unwrap(), - ExportsResolution::Path(PathBuf::from("/foo/foo.mjs")) - ); - assert_eq!( - pkg - .resolve_package_imports("internal/foo", ExportsCondition::empty(), &[]) - .unwrap(), - ExportsResolution::Path(PathBuf::from("/foo/src/internal/foo.mjs")) - ); - assert_eq!( - pkg - .resolve_package_imports("bar", ExportsCondition::empty(), &[]) - .unwrap(), - ExportsResolution::Package("bar".into()) - ); - } - - #[test] - fn import_conditions() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - imports: indexmap! { - "#entry/*".into() => ExportsField::Map(indexmap! { - "node".into() => ExportsField::String("./node/*.js"), - "browser".into() => ExportsField::String("./browser/*.js") - }) - }, - ..PackageJson::default() - }; - assert_eq!( - pkg - .resolve_package_imports("entry/foo", ExportsCondition::NODE, &[]) - .unwrap(), - ExportsResolution::Path(PathBuf::from("/foo/node/foo.js")) - ); - assert_eq!( - pkg - .resolve_package_imports("entry/foo", ExportsCondition::BROWSER, &[]) - .unwrap(), - ExportsResolution::Path(PathBuf::from("/foo/browser/foo.js")) - ); - assert_eq!( - pkg - .resolve_package_imports( - "entry/foo", - ExportsCondition::NODE | ExportsCondition::BROWSER, - &[] - ) - .unwrap(), - ExportsResolution::Path(PathBuf::from("/foo/node/foo.js")) - ); - } - - #[test] - fn aliases() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - alias: indexmap! { - "./foo.js".into() => AliasValue::Specifier("./foo-alias.js".into()), - "bar".into() => AliasValue::Specifier("./bar-alias.js".into()), - "lodash".into() => AliasValue::Specifier("my-lodash".into()), - "lodash/clone".into() => AliasValue::Specifier("./clone.js".into()), - "test".into() => AliasValue::Specifier("./test".into()), - "foo/*".into() => AliasValue::Specifier("bar/$1".into()), - "./foo/src/**".into() => AliasValue::Specifier("./foo/lib/$1".into()), - "/foo/src/**".into() => AliasValue::Specifier("/foo/lib/$1".into()), - "~/foo/src/**".into() => AliasValue::Specifier("~/foo/lib/$1".into()), - "url".into() => AliasValue::Bool(false), - "@internal/**".into() => AliasValue::Specifier("./internal/$1".into()), - "@foo/*/bar/*".into() => AliasValue::Specifier("./test/$1/$2".into()), - }, - ..PackageJson::default() - }; - - assert_eq!( - pkg.resolve_aliases(&"./foo.js".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./foo-alias.js".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"bar".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./bar-alias.js".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"lodash".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("my-lodash".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"lodash/foo".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("my-lodash/foo".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"lodash/clone".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./clone.js".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"test".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./test".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"test/foo".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./test/foo".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"foo/hi".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("bar/hi".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"./foo/src/a/b".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./foo/lib/a/b".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"/foo/src/a/b".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("/foo/lib/a/b".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"~/foo/src/a/b".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("~/foo/lib/a/b".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"url".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Bool(false))) - ); - assert_eq!( - pkg.resolve_aliases(&"@internal/foo".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./internal/foo".into()))) - ); - assert_eq!( - pkg.resolve_aliases(&"@internal/foo/bar".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier( - "./internal/foo/bar".into() - ))) - ); - assert_eq!( - pkg.resolve_aliases(&"@foo/a/bar/b".into(), Fields::ALIAS), - Some(Cow::Owned(AliasValue::Specifier("./test/a/b".into()))) - ); - } - - #[allow(clippy::single_range_in_vec_init)] - #[test] - fn test_replace_captures() { - assert_eq!( - replace_captures("test/$1/$2", "foo/bar/baz", &vec![4..7, 8..11]), - Cow::Borrowed("test/bar/baz") - ); - assert_eq!( - replace_captures("test/$1/$2", "foo/bar/baz", &vec![4..7]), - Cow::Borrowed("test/bar/$2") - ); - assert_eq!( - replace_captures("test/$1/$2/$3", "foo/bar/baz", &vec![4..7, 8..11]), - Cow::Borrowed("test/bar/baz/$3") - ); - assert_eq!( - replace_captures("test/$1/$2/$", "foo/bar/baz", &vec![4..7, 8..11]), - Cow::Borrowed("test/bar/baz/$") - ); - assert_eq!( - replace_captures("te$st/$1/$2", "foo/bar/baz", &vec![4..7, 8..11]), - Cow::Borrowed("te$st/bar/baz") - ); - } - - #[test] - fn side_effects_none() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - ..PackageJson::default() - }; - - assert!(pkg.has_side_effects(Path::new("/foo/index.js"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/index.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - } - - #[test] - fn side_effects_bool() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - side_effects: SideEffects::Boolean(false), - ..PackageJson::default() - }; - - assert!(!pkg.has_side_effects(Path::new("/foo/index.js"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/index.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - - let pkg = PackageJson { - side_effects: SideEffects::Boolean(true), - ..pkg - }; - - assert!(pkg.has_side_effects(Path::new("/foo/index.js"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/index.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - } - - #[test] - fn side_effects_glob() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - side_effects: SideEffects::String("*.css"), - ..PackageJson::default() - }; - - assert!(pkg.has_side_effects(Path::new("/foo/a.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.css"))); - assert!(!pkg.has_side_effects(Path::new("/foo/a.js"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - - let pkg = PackageJson { - side_effects: SideEffects::String("bar/*.css"), - ..pkg - }; - - assert!(!pkg.has_side_effects(Path::new("/foo/a.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/x/baz.css"))); - assert!(!pkg.has_side_effects(Path::new("/foo/a.js"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - - let pkg = PackageJson { - side_effects: SideEffects::String("./bar/*.css"), - ..pkg - }; - - assert!(!pkg.has_side_effects(Path::new("/foo/a.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/x/baz.css"))); - assert!(!pkg.has_side_effects(Path::new("/foo/a.js"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - } - - #[test] - fn side_effects_array() { - let pkg = PackageJson { - path: "/foo/package.json".into(), - name: "foobar", - side_effects: SideEffects::Array(vec!["*.css", "*.html"]), - ..PackageJson::default() - }; - - assert!(pkg.has_side_effects(Path::new("/foo/a.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.css"))); - assert!(pkg.has_side_effects(Path::new("/foo/a.html"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/baz.html"))); - assert!(pkg.has_side_effects(Path::new("/foo/bar/x/baz.html"))); - assert!(!pkg.has_side_effects(Path::new("/foo/a.js"))); - assert!(!pkg.has_side_effects(Path::new("/foo/bar/baz.js"))); - assert!(pkg.has_side_effects(Path::new("/index.js"))); - } - - #[test] - fn parsing() { - let pkg: PackageJson = serde_json::from_str(r#"{"type":"script"}"#).unwrap(); - assert_eq!(pkg.module_type, ModuleType::CommonJs); - let pkg: PackageJson = serde_json::from_str(r#"{"name":"foo"}"#).unwrap(); - assert_eq!(pkg.module_type, ModuleType::CommonJs); - } -} diff --git a/packages/utils/node-resolver-rs-old/src/path.rs b/packages/utils/node-resolver-rs-old/src/path.rs deleted file mode 100644 index 4e27412c3cf..00000000000 --- a/packages/utils/node-resolver-rs-old/src/path.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -pub fn normalize_path(path: &Path) -> PathBuf { - // Normalize path components to resolve ".." and "." segments. - // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61 - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - - ret -} - -pub fn resolve_path, B: AsRef>(base: A, subpath: B) -> PathBuf { - let subpath = subpath.as_ref(); - let mut components = subpath.components().peekable(); - if subpath.is_absolute() || matches!(components.peek(), Some(Component::Prefix(..))) { - return subpath.to_path_buf(); - } - - let mut ret = base.as_ref().to_path_buf(); - ret.pop(); - for component in subpath.components() { - match component { - Component::Prefix(..) | Component::RootDir => unreachable!(), - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - - ret -} diff --git a/packages/utils/node-resolver-rs-old/src/specifier.rs b/packages/utils/node-resolver-rs-old/src/specifier.rs deleted file mode 100644 index e5b52976045..00000000000 --- a/packages/utils/node-resolver-rs-old/src/specifier.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::borrow::Cow; -use std::path::is_separator; -use std::path::Path; -use std::path::PathBuf; - -use percent_encoding::percent_decode_str; - -use crate::builtins::BUILTINS; -use crate::url_to_path::url_to_path; -use crate::Flags; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum SpecifierType { - Esm, - Cjs, - Url, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize)] -#[serde(tag = "kind", content = "value")] -pub enum SpecifierError { - EmptySpecifier, - InvalidPackageSpecifier, - #[serde(serialize_with = "serialize_url_error")] - UrlError(url::ParseError), - InvalidFileUrl, -} - -impl From for SpecifierError { - fn from(value: url::ParseError) -> Self { - SpecifierError::UrlError(value) - } -} - -fn serialize_url_error(value: &url::ParseError, serializer: S) -> Result -where - S: serde::Serializer, -{ - use serde::Serialize; - value.to_string().serialize(serializer) -} - -#[derive(PartialEq, Eq, Hash, Clone, Debug)] -pub enum Specifier<'a> { - Relative(Cow<'a, Path>), - Absolute(Cow<'a, Path>), - Tilde(Cow<'a, Path>), - Hash(Cow<'a, str>), - Package(Cow<'a, str>, Cow<'a, str>), - Builtin(Cow<'a, str>), - Url(&'a str), -} - -impl<'a> Specifier<'a> { - pub fn parse( - specifier: &'a str, - specifier_type: SpecifierType, - flags: Flags, - ) -> Result<(Specifier<'a>, Option<&'a str>), SpecifierError> { - if specifier.is_empty() { - return Err(SpecifierError::EmptySpecifier); - } - - Ok(match specifier.as_bytes()[0] { - b'.' => { - let specifier = if let Some(specifier) = specifier.strip_prefix("./") { - specifier.trim_start_matches('/') - } else { - specifier - }; - let (path, query) = decode_path(specifier, specifier_type); - (Specifier::Relative(path), query) - } - b'~' => { - let mut specifier = &specifier[1..]; - if !specifier.is_empty() && is_separator(specifier.as_bytes()[0] as char) { - specifier = &specifier[1..]; - } - let (path, query) = decode_path(specifier, specifier_type); - (Specifier::Tilde(path), query) - } - b'/' => { - if specifier.starts_with("//") && specifier_type == SpecifierType::Url { - // A protocol-relative URL, e.g `url('//example.com/foo.png')`. - (Specifier::Url(specifier), None) - } else { - let (path, query) = decode_path(specifier, specifier_type); - (Specifier::Absolute(path), query) - } - } - b'#' => (Specifier::Hash(Cow::Borrowed(&specifier[1..])), None), - _ => { - // Bare specifier. - match specifier_type { - SpecifierType::Url | SpecifierType::Esm => { - // Check if there is a scheme first. - if let Ok((scheme, rest)) = parse_scheme(specifier) { - let (path, rest) = parse_path(rest); - let (query, _) = parse_query(rest); - match scheme.as_ref() { - "npm" if flags.contains(Flags::NPM_SCHEME) => { - if BUILTINS.contains(&path) { - return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); - } - - ( - parse_package(percent_decode_str(path).decode_utf8_lossy())?, - query, - ) - } - "node" => { - // Node does not URL decode or support query params here. - // See https://github.com/nodejs/node/issues/39710. - (Specifier::Builtin(Cow::Borrowed(path)), None) - } - "file" => ( - Specifier::Absolute(Cow::Owned(url_to_path(specifier)?)), - query, - ), - _ => (Specifier::Url(specifier), None), - } - } else { - // If not, then parse as an npm package if this is an ESM specifier, - // otherwise treat this as a relative path. - let (path, rest) = parse_path(specifier); - if specifier_type == SpecifierType::Esm { - if BUILTINS.contains(&path) { - return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); - } - - let (query, _) = parse_query(rest); - ( - parse_package(percent_decode_str(path).decode_utf8_lossy())?, - query, - ) - } else { - let (path, query) = decode_path(specifier, specifier_type); - (Specifier::Relative(path), query) - } - } - } - SpecifierType::Cjs => { - if let Some(node_prefixed) = specifier.strip_prefix("node:") { - return Ok((Specifier::Builtin(Cow::Borrowed(node_prefixed)), None)); - } - - if BUILTINS.contains(&specifier) { - (Specifier::Builtin(Cow::Borrowed(specifier)), None) - } else { - #[cfg(windows)] - if !flags.contains(Flags::ABSOLUTE_SPECIFIERS) { - let path = Path::new(specifier); - if path.is_absolute() { - return Ok((Specifier::Absolute(Cow::Borrowed(path)), None)); - } - } - - (parse_package(Cow::Borrowed(specifier))?, None) - } - } - } - } - }) - } - - pub fn to_string(&'a self) -> Cow<'a, str> { - match self { - Specifier::Relative(path) | Specifier::Absolute(path) | Specifier::Tilde(path) => { - path.as_os_str().to_string_lossy() - } - Specifier::Hash(path) => path.clone(), - Specifier::Package(module, subpath) => { - if subpath.is_empty() { - Cow::Borrowed(module) - } else { - Cow::Owned(format!("{}/{}", module, subpath)) - } - } - Specifier::Builtin(builtin) => Cow::Borrowed(builtin), - Specifier::Url(url) => Cow::Borrowed(url), - } - } -} - -// https://url.spec.whatwg.org/#scheme-state -// https://github.com/servo/rust-url/blob/1c1e406874b3d2aa6f36c5d2f3a5c2ea74af9efb/url/src/parser.rs#L387 -pub fn parse_scheme(input: &str) -> Result<(Cow<'_, str>, &str), ()> { - if input.is_empty() || !input.starts_with(ascii_alpha) { - return Err(()); - } - let mut is_lowercase = true; - for (i, c) in input.chars().enumerate() { - match c { - 'A'..='Z' => { - is_lowercase = false; - } - 'a'..='z' | '0'..='9' | '+' | '-' | '.' => {} - ':' => { - let scheme = &input[0..i]; - let rest = &input[i + 1..]; - return Ok(if is_lowercase { - (Cow::Borrowed(scheme), rest) - } else { - (Cow::Owned(scheme.to_ascii_lowercase()), rest) - }); - } - _ => { - return Err(()); - } - } - } - - // EOF before ':' - Err(()) -} - -// https://url.spec.whatwg.org/#path-state -fn parse_path(input: &str) -> (&str, &str) { - // We don't really want to normalize the path (e.g. replacing ".." and "." segments). - // That is done later. For now, we just need to find the end of the path. - if let Some(pos) = input.chars().position(|c| c == '?' || c == '#') { - (&input[0..pos], &input[pos..]) - } else { - (input, "") - } -} - -// https://url.spec.whatwg.org/#query-state -fn parse_query(input: &str) -> (Option<&str>, &str) { - if !input.is_empty() && input.as_bytes()[0] == b'?' { - if let Some(pos) = input.chars().position(|c| c == '#') { - (Some(&input[0..pos]), &input[pos..]) - } else { - (Some(input), "") - } - } else { - (None, input) - } -} - -/// https://url.spec.whatwg.org/#ascii-alpha -#[inline] -fn ascii_alpha(ch: char) -> bool { - ch.is_ascii_alphabetic() -} - -fn parse_package(specifier: Cow<'_, str>) -> Result { - match specifier { - Cow::Borrowed(specifier) => { - let (module, subpath) = parse_package_specifier(specifier)?; - Ok(Specifier::Package( - Cow::Borrowed(module), - Cow::Borrowed(subpath), - )) - } - Cow::Owned(specifier) => { - let (module, subpath) = parse_package_specifier(&specifier)?; - Ok(Specifier::Package( - Cow::Owned(module.to_owned()), - Cow::Owned(subpath.to_owned()), - )) - } - } -} - -pub fn parse_package_specifier(specifier: &str) -> Result<(&str, &str), SpecifierError> { - let idx = specifier.chars().position(|p| p == '/'); - if specifier.starts_with('@') { - let idx = idx.ok_or(SpecifierError::InvalidPackageSpecifier)?; - if let Some(next) = &specifier[idx + 1..].chars().position(|p| p == '/') { - Ok(( - &specifier[0..idx + 1 + *next], - &specifier[idx + *next + 2..], - )) - } else { - Ok((specifier, "")) - } - } else if let Some(idx) = idx { - Ok((&specifier[0..idx], &specifier[idx + 1..])) - } else { - Ok((specifier, "")) - } -} - -pub fn decode_path( - specifier: &str, - specifier_type: SpecifierType, -) -> (Cow<'_, Path>, Option<&str>) { - match specifier_type { - SpecifierType::Url | SpecifierType::Esm => { - let (path, rest) = parse_path(specifier); - let (query, _) = parse_query(rest); - let path = match percent_decode_str(path).decode_utf8_lossy() { - Cow::Borrowed(v) => Cow::Borrowed(Path::new(v)), - Cow::Owned(v) => Cow::Owned(PathBuf::from(v)), - }; - (path, query) - } - SpecifierType::Cjs => (Cow::Borrowed(Path::new(specifier)), None), - } -} - -impl<'a> From<&'a str> for Specifier<'a> { - fn from(specifier: &'a str) -> Self { - Specifier::parse(specifier, SpecifierType::Cjs, Flags::empty()) - .unwrap() - .0 - } -} - -impl<'a, 'de: 'a> serde::Deserialize<'de> for Specifier<'a> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::Deserialize; - let s: &'de str = Deserialize::deserialize(deserializer)?; - // Specifiers are only deserialized as part of the "alias" and "browser" fields, - // so we assume CJS specifiers in Parcel mode. - Specifier::parse(s, SpecifierType::Cjs, Flags::empty()) - .map(|s| s.0) - .map_err(|_| serde::de::Error::custom("Invalid specifier")) - } -} diff --git a/packages/utils/node-resolver-rs-old/src/tsconfig.rs b/packages/utils/node-resolver-rs-old/src/tsconfig.rs deleted file mode 100644 index 65ed15eb72c..00000000000 --- a/packages/utils/node-resolver-rs-old/src/tsconfig.rs +++ /dev/null @@ -1,309 +0,0 @@ -use std::borrow::Cow; -use std::path::Path; -use std::path::PathBuf; - -use indexmap::IndexMap; -use itertools::Either; -#[allow(deprecated)] -use json_comments::strip_comments_in_place; - -use crate::path::resolve_path; -use crate::specifier::Specifier; - -#[derive(serde::Deserialize, Debug, Default)] -#[serde(rename_all = "camelCase")] -pub struct TsConfig<'a> { - #[serde(skip)] - pub path: PathBuf, - base_url: Option>, - #[serde(borrow)] - paths: Option, Vec<&'a str>>>, - #[serde(skip)] - paths_base: PathBuf, - pub module_suffixes: Option>, - // rootDirs?? -} - -fn deserialize_extends<'a, 'de: 'a, D>(deserializer: D) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - use serde::Deserialize; - - #[derive(serde::Deserialize)] - #[serde(untagged)] - enum StringOrArray<'a> { - #[serde(borrow)] - String(Specifier<'a>), - Array(Vec>), - } - - Ok(match StringOrArray::deserialize(deserializer)? { - StringOrArray::String(s) => vec![s], - StringOrArray::Array(a) => a, - }) -} - -#[derive(serde::Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct TsConfigWrapper<'a> { - #[serde(borrow, default, deserialize_with = "deserialize_extends")] - pub extends: Vec>, - #[serde(default)] - pub compiler_options: TsConfig<'a>, -} - -impl<'a> TsConfig<'a> { - pub fn parse(path: PathBuf, data: &'a mut str) -> serde_json::Result> { - #[allow(deprecated)] - let _ = strip_comments_in_place(data, Default::default(), true); - let mut wrapper: TsConfigWrapper = serde_json::from_str(data)?; - wrapper.compiler_options.path = path; - wrapper.compiler_options.validate(); - Ok(wrapper) - } - - fn validate(&mut self) { - if let Some(base_url) = &mut self.base_url { - *base_url = Cow::Owned(resolve_path(&self.path, &base_url)); - } - - if self.paths.is_some() { - self.paths_base = if let Some(base_url) = &self.base_url { - base_url.as_ref().to_owned() - } else { - self.path.parent().unwrap().to_owned() - }; - } - } - - pub fn extend(&mut self, extended: &TsConfig<'a>) { - if self.base_url.is_none() { - self.base_url = extended.base_url.clone(); - } - - if self.paths.is_none() { - self.paths_base = extended.paths_base.clone(); - self.paths = extended.paths.clone(); - } - - if self.module_suffixes.is_none() { - self.module_suffixes = extended.module_suffixes.clone(); - } - } - - pub fn paths(&'a self, specifier: &'a Specifier) -> impl Iterator + 'a { - if !matches!(specifier, Specifier::Package(..) | Specifier::Builtin(..)) { - return Either::Right(Either::Right(std::iter::empty())); - } - - // If there is a base url setting, resolve it relative to the tsconfig.json file. - // Otherwise, the base for paths is implicitly the directory containing the tsconfig. - let base_url_iter = if let Some(base_url) = &self.base_url { - Either::Left(base_url_iter(base_url, specifier)) - } else { - Either::Right(std::iter::empty()) - }; - - if let Some(paths) = &self.paths { - // Check exact match first. - if let Some(paths) = paths.get(specifier) { - return Either::Left(join_paths(&self.paths_base, paths, None).chain(base_url_iter)); - } - - // Check patterns - let mut longest_prefix_length = 0; - let mut longest_suffix_length = 0; - let mut best_key = None; - let full_specifier = specifier.to_string(); - - for key in paths.keys() { - let path = key.to_string(); - if let Some((prefix, suffix)) = path.split_once('*') { - if (best_key.is_none() || prefix.len() > longest_prefix_length) - && full_specifier.starts_with(prefix) - && full_specifier.ends_with(suffix) - { - longest_prefix_length = prefix.len(); - longest_suffix_length = suffix.len(); - best_key = Some(key); - } - } - } - - if let Some(key) = best_key { - let paths = paths.get(key).unwrap(); - return Either::Left( - join_paths( - &self.paths_base, - paths, - Some((full_specifier, longest_prefix_length, longest_suffix_length)), - ) - .chain(base_url_iter), - ); - } - } - - if matches!(specifier, Specifier::Builtin(..)) { - // If specifier is a builtin then there's no match - return Either::Right(Either::Right(std::iter::empty())); - } - - // If no paths were found, try relative to the base url. - Either::Right(base_url_iter) - } -} - -fn join_paths<'a>( - base_url: &'a Path, - paths: &'a [&'a str], - replacement: Option<(Cow<'a, str>, usize, usize)>, -) -> impl Iterator + 'a { - paths - .iter() - .filter(|p| !p.ends_with(".d.ts")) - .map(move |path| { - if let Some((replacement, start, end)) = &replacement { - let path = path.replace('*', &replacement[*start..replacement.len() - *end]); - base_url.join(path) - } else { - base_url.join(path) - } - }) -} - -fn base_url_iter<'a>( - base_url: &'a Path, - specifier: &'a Specifier, -) -> impl Iterator + 'a { - std::iter::once_with(move || { - let mut path = base_url.to_owned(); - if let Specifier::Package(module, subpath) = specifier { - path.push(module.as_ref()); - path.push(subpath.as_ref()); - } - path - }) -} - -#[cfg(test)] -mod tests { - use indexmap::indexmap; - - use super::*; - - #[test] - fn test_paths() { - let mut tsconfig = TsConfig { - path: "/foo/tsconfig.json".into(), - paths: Some(indexmap! { - "jquery".into() => vec!["node_modules/jquery/dist/jquery"], - "*".into() => vec!["generated/*"], - "bar/*".into() => vec!["test/*"], - "bar/baz/*".into() => vec!["baz/*", "yo/*"], - "@/components/*".into() => vec!["components/*"], - "url".into() => vec!["node_modules/my-url"], - }), - ..Default::default() - }; - tsconfig.validate(); - - let test = |specifier: &str| tsconfig.paths(&specifier.into()).collect::>(); - - assert_eq!( - test("jquery"), - vec![PathBuf::from("/foo/node_modules/jquery/dist/jquery")] - ); - assert_eq!(test("test"), vec![PathBuf::from("/foo/generated/test")]); - assert_eq!( - test("test/hello"), - vec![PathBuf::from("/foo/generated/test/hello")] - ); - assert_eq!(test("bar/hi"), vec![PathBuf::from("/foo/test/hi")]); - assert_eq!( - test("bar/baz/hi"), - vec![PathBuf::from("/foo/baz/hi"), PathBuf::from("/foo/yo/hi")] - ); - assert_eq!( - test("@/components/button"), - vec![PathBuf::from("/foo/components/button")] - ); - assert_eq!(test("./jquery"), Vec::::new()); - assert_eq!(test("url"), vec![PathBuf::from("/foo/node_modules/my-url")]); - } - - #[test] - fn test_base_url() { - let mut tsconfig = TsConfig { - path: "/foo/tsconfig.json".into(), - base_url: Some(Path::new("src").into()), - ..Default::default() - }; - tsconfig.validate(); - - let test = |specifier: &str| tsconfig.paths(&specifier.into()).collect::>(); - - assert_eq!(test("foo"), vec![PathBuf::from("/foo/src/foo")]); - assert_eq!( - test("components/button"), - vec![PathBuf::from("/foo/src/components/button")] - ); - assert_eq!(test("./jquery"), Vec::::new()); - } - - #[test] - fn test_paths_and_base_url() { - let mut tsconfig = TsConfig { - path: "/foo/tsconfig.json".into(), - base_url: Some(Path::new("src").into()), - paths: Some(indexmap! { - "*".into() => vec!["generated/*"], - "bar/*".into() => vec!["test/*"], - "bar/baz/*".into() => vec!["baz/*", "yo/*"], - "@/components/*".into() => vec!["components/*"], - }), - ..Default::default() - }; - tsconfig.validate(); - - let test = |specifier: &str| tsconfig.paths(&specifier.into()).collect::>(); - - assert_eq!( - test("test"), - vec![ - PathBuf::from("/foo/src/generated/test"), - PathBuf::from("/foo/src/test") - ] - ); - assert_eq!( - test("test/hello"), - vec![ - PathBuf::from("/foo/src/generated/test/hello"), - PathBuf::from("/foo/src/test/hello") - ] - ); - assert_eq!( - test("bar/hi"), - vec![ - PathBuf::from("/foo/src/test/hi"), - PathBuf::from("/foo/src/bar/hi") - ] - ); - assert_eq!( - test("bar/baz/hi"), - vec![ - PathBuf::from("/foo/src/baz/hi"), - PathBuf::from("/foo/src/yo/hi"), - PathBuf::from("/foo/src/bar/baz/hi") - ] - ); - assert_eq!( - test("@/components/button"), - vec![ - PathBuf::from("/foo/src/components/button"), - PathBuf::from("/foo/src/@/components/button") - ] - ); - assert_eq!(test("./jquery"), Vec::::new()); - } -} diff --git a/packages/utils/node-resolver-rs-old/src/url_to_path.rs b/packages/utils/node-resolver-rs-old/src/url_to_path.rs deleted file mode 100644 index 2eeffebcdb7..00000000000 --- a/packages/utils/node-resolver-rs-old/src/url_to_path.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! An implementation url.to_file_path that behaves like Unix on Wasm -#![allow(clippy::items_after_test_module)] - -#[cfg(any(target_arch = "wasm32", test))] -use std::ffi::OsStr; -use std::path::PathBuf; - -use url::Url; - -use crate::specifier::SpecifierError; - -pub fn url_to_path(input: &str) -> Result { - let url = Url::parse(input)?; - - #[cfg(target_arch = "wasm32")] - { - Ok(to_file_path(&url).map_err(|_| SpecifierError::InvalidFileUrl)?) - } - - #[cfg(not(target_arch = "wasm32"))] - { - url - .to_file_path() - .map_err(|_| SpecifierError::InvalidFileUrl) - } -} - -// From std::os::unix::ffi::os_str.rs (also used on WASI) -#[cfg(any(target_arch = "wasm32", test))] -#[inline] -fn os_str_from_bytes(slice: &[u8]) -> &OsStr { - unsafe { std::mem::transmute(slice) } -} - -#[cfg(test)] -mod test { - use std::path::PathBuf; - - use url::Url; - - use crate::url_to_path::to_file_path; - - #[test] - fn test() { - let f = "/x/y/z/foo.js"; - assert_eq!( - to_file_path(&Url::parse(&format!("file://{f}")).unwrap()), - Ok(PathBuf::from(f)) - ); - let f = "/bar.js"; - assert_eq!( - to_file_path(&Url::parse(&format!("file://{f}")).unwrap()), - Ok(PathBuf::from(f)) - ); - } -} - -// The functions below are copied from https://github.com/servo/rust-url/blob/74b8694568d8eb936e339a7d726bda46881dcd9d/url/src/lib.rs - -// Copyright (c) 2013-2022 The rust-url developers - -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: - -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#[cfg(any(target_arch = "wasm32", test))] -pub fn to_file_path(this: &Url) -> Result { - use url::Host; - - if let Some(segments) = this.path_segments() { - let host = match this.host() { - None | Some(Host::Domain("localhost")) => None, - // Some(_) if cfg!(windows) && this.scheme() == "file" => { - // Some(&this.serialization[this.host_start as usize..this.host_end as usize]) - // } - _ => return Err(()), - }; - - return file_url_segments_to_pathbuf(host, segments); - } - Err(()) -} - -#[allow(clippy::manual_is_ascii_check)] -#[cfg(any(target_arch = "wasm32", test))] -fn file_url_segments_to_pathbuf( - host: Option<&str>, - segments: core::str::Split<'_, char>, -) -> Result { - use percent_encoding::percent_decode; - - if host.is_some() { - return Err(()); - } - - let mut bytes = - // if cfg!(target_os = "redox") { - // b"file:".to_vec() - // } else { - Vec::new() - // } - ; - - for segment in segments { - bytes.push(b'/'); - bytes.extend(percent_decode(segment.as_bytes())); - } - - // A windows drive letter must end with a slash. - if bytes.len() > 2 - && matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z') - && matches!(bytes[bytes.len() - 1], b':' | b'|') - { - bytes.push(b'/'); - } - - let os_str = os_str_from_bytes(&bytes); - let path = PathBuf::from(os_str); - - debug_assert!( - path.has_root(), // path.is_absolute(), - "to_file_path() failed to produce an absolute Path" - ); - - Ok(path) -} diff --git a/packages/utils/node-resolver-rs/Cargo.toml b/packages/utils/node-resolver-rs/Cargo.toml index 8f5a11eaf1d..5e71f7738a6 100644 --- a/packages/utils/node-resolver-rs/Cargo.toml +++ b/packages/utils/node-resolver-rs/Cargo.toml @@ -4,36 +4,25 @@ name = "parcel-resolver" version = "0.1.0" edition = "2021" -[[bench]] -name = "node_resolver_bench" -harness = false - [dependencies] -parcel_core = { path = "../../../crates/parcel_core" } -parcel_filesystem = { path = "../../../crates/parcel_filesystem" } - bitflags = "1.3.2" dashmap = "5.4.0" +elsa = "1.7.0" glob-match = "0.2.1" indexmap = { version = "1.9.2", features = ["serde"] } itertools = "0.10.5" json_comments = { path = "../../../crates/json-comments-rs" } once_cell = "1.17.0" parking_lot = "0.12" -serde_json5 = "0.1.0" percent-encoding = "2.2.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.91" -thiserror = "1.0.59" +typed-arena = "2.0.2" url = "2.3.1" xxhash-rust = { version = "0.8.2", features = ["xxh3"] } -tracing = "0.1.40" -tracing-subscriber = "0.3.18" [dev-dependencies] assert_fs = "1.0" -criterion = "0.5.1" -parcel-resolver-old = { path = "../node-resolver-rs-old" } [target.'cfg(windows)'.dev-dependencies] is_elevated = "0.1.2" diff --git a/packages/utils/node-resolver-rs/benches/node_resolver_bench.rs b/packages/utils/node-resolver-rs/benches/node_resolver_bench.rs deleted file mode 100644 index 4025fcb9cf2..00000000000 --- a/packages/utils/node-resolver-rs/benches/node_resolver_bench.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! This file contains a few benchmarks which demonstrate the following insights on the resolver -//! performance profile: -//! -//! 1. Resolution completes in micro-seconds -//! 2. Resolution is IO bound; if we remove all IO resolution is around 3-4x faster, therefore we -//! can estimate most of the time is spent doing IO and not anything else -//! 3. stat is faster than read_to_string if files don't exist by around 2x on macOS and 3x on Linux -//! therefore, we should only read files after we've checked they exist, it is worth it to check -//! if the file is present before reading if we will miss files a large proportion of the time -//! 4. The next bottleneck is JSON parsing. To that we are using serde_json5, the master branch is -//! 20-30% faster than the latest version on `crates.io` (which is 0.1 as of time of writing). -//! We can have much faster JSON parsing if we look into consuming SIMD-JSON. -//! -use std::collections::HashMap; -use std::hint::black_box; -use std::path::{Component, Path, PathBuf}; -use std::sync::Arc; - -use criterion::{criterion_group, criterion_main, Criterion}; -use parking_lot::RwLock; - -use parcel_filesystem::os_file_system::OsFileSystem; -use parcel_filesystem::FileSystem; -use parcel_resolver::{Cache, CacheCow, Resolver, SpecifierType}; - -#[derive(Clone)] -enum FileEntry { - Directory, - File(String), -} - -struct PreloadingFileSystem { - files: RwLock>, -} - -impl Clone for PreloadingFileSystem { - fn clone(&self) -> Self { - let files = self.files.read(); - Self { - files: RwLock::new(files.clone()), - } - } -} - -impl PreloadingFileSystem { - fn load(root: &Path) -> Self { - let mut files = HashMap::new(); - fn load_directory(files: &mut HashMap, dir: &Path) { - files.insert(dir.to_path_buf(), FileEntry::Directory); - let entries = std::fs::read_dir(dir).unwrap(); - for entry in entries { - let entry = entry.unwrap(); - let path = entry.path(); - if path.is_file() { - let string = std::fs::read_to_string(&path).unwrap(); - files.insert(path, FileEntry::File(string)); - } else { - load_directory(files, &path) - } - } - } - load_directory(&mut files, root); - - let files = RwLock::new(files); - Self { files } - } -} - -impl FileSystem for PreloadingFileSystem { - fn cwd(&self) -> std::io::Result { - todo!() - } - - fn canonicalize_base(&self, path: &Path) -> std::io::Result { - let cwd = Path::new("/"); - let mut result = if path.is_absolute() { - vec![] - } else { - cwd.components().collect() - }; - - let components = path.components(); - for component in components { - match component { - Component::Prefix(prefix) => { - result = vec![Component::Prefix(prefix)]; - } - Component::RootDir => { - result.push(Component::RootDir); - } - Component::CurDir => {} - Component::ParentDir => { - result.pop(); - } - Component::Normal(path) => { - result.push(Component::Normal(path)); - } - } - } - - Ok(PathBuf::from_iter(result)) - } - - fn create_directory(&self, path: &Path) -> std::io::Result<()> { - self - .files - .write() - .insert(path.to_path_buf(), FileEntry::Directory); - Ok(()) - } - - fn read_to_string(&self, path: &Path) -> std::io::Result { - let files = self.files.read(); - let file = files.get(path); - if let Some(FileEntry::File(contents)) = file { - Ok(contents.to_string()) - } else { - #[allow(unreachable_code)] - return todo!(); - } - } - - fn is_file(&self, path: &Path) -> bool { - let files = self.files.read(); - let file = files.get(path); - matches!(file, Some(FileEntry::File(_))) - } - - fn is_dir(&self, path: &Path) -> bool { - let files = self.files.read(); - let file = files.get(path); - matches!(file, Some(FileEntry::Directory)) - } -} - -fn root() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("node-resolver-core/test/fixture") -} - -fn criterion_benchmark(c: &mut Criterion) { - let make_resolver = || { - Resolver::parcel( - root().into(), - CacheCow::Owned(Cache::new(Arc::new(OsFileSystem))), - ) - }; - c.bench_function( - "FileSystem - check for non existent file using stat (exists)", - |b| { - let target = root().join("do-not-exist"); - b.iter(|| black_box(target.exists())); - }, - ); - - c.bench_function( - "FileSystem - check for non existent file open (read_to_string)", - |b| { - let target = root().join("do-not-exist"); - b.iter(|| black_box(std::fs::read_to_string(&target).is_err())); - }, - ); - - c.bench_function("Run safe resolver simple OsFileSystem", |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve("./bar.js", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap(); - black_box(result) - }, - ); - }); - - c.bench_function("Run safe resolver modules OsFileSystem", |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Cjs) - .result - .unwrap(); - black_box(result) - }, - ); - }); - - let make_resolver = || { - parcel_resolver_old::Resolver::parcel( - root().into(), - parcel_resolver_old::CacheCow::Owned(parcel_resolver_old::Cache::new(Arc::new(OsFileSystem))), - ) - }; - - c.bench_function("Run unsafe resolver simple OsFileSystem", |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve( - "./bar.js", - &root().join("foo.js"), - parcel_resolver_old::SpecifierType::Esm, - ) - .result - .unwrap(); - black_box(result) - }, - ); - }); - - c.bench_function("Run unsafe resolver modules OsFileSystem", |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve( - "@scope/pkg", - &root().join("foo.js"), - parcel_resolver_old::SpecifierType::Cjs, - ) - .result - .unwrap(); - black_box(result) - }, - ); - }); - - let preloading_fs = PreloadingFileSystem::load(&root()); - let make_resolver = || { - Resolver::parcel( - root().into(), - CacheCow::Owned(Cache::new(Arc::new(preloading_fs.clone()))), - ) - }; - - c.bench_function( - "Run safe resolver simple - PreloadingFileSystem - No IO", - |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve("./bar.js", &root().join("foo.js"), SpecifierType::Esm) - .result - .unwrap(); - black_box(result) - }, - ); - }, - ); - - c.bench_function( - "Run safe resolver modules - PreloadingFileSystem - No IO", - |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Cjs) - .result - .unwrap(); - black_box(result) - }, - ); - }, - ); - - let make_resolver = || { - parcel_resolver_old::Resolver::parcel( - root().into(), - parcel_resolver_old::CacheCow::Owned(parcel_resolver_old::Cache::new(Arc::new( - preloading_fs.clone(), - ))), - ) - }; - - c.bench_function( - "Run unsafe resolver simple - PreloadingFileSystem - No IO", - |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve( - "./bar.js", - &root().join("foo.js"), - parcel_resolver_old::SpecifierType::Esm, - ) - .result - .unwrap(); - black_box(result) - }, - ); - }, - ); - - c.bench_function( - "Run unsafe resolver modules - PreloadingFileSystem - No IO", - |b| { - b.iter_with_setup( - || make_resolver(), - |resolver| { - let result = resolver - .resolve( - "@scope/pkg", - &root().join("foo.js"), - parcel_resolver_old::SpecifierType::Cjs, - ) - .result - .unwrap(); - black_box(result) - }, - ); - }, - ); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/packages/utils/node-resolver-rs/src/cache.rs b/packages/utils/node-resolver-rs/src/cache.rs index a20dd3c2cb2..71d7dd5d70b 100644 --- a/packages/utils/node-resolver-rs/src/cache.rs +++ b/packages/utils/node-resolver-rs/src/cache.rs @@ -1,39 +1,39 @@ -use std::borrow::Cow; -use std::fmt; -use std::ops::Deref; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; +use std::{ + borrow::Cow, + fmt, + ops::Deref, + path::{Path, PathBuf}, + sync::Arc, +}; use dashmap::DashMap; +use elsa::sync::FrozenMap; +use parking_lot::Mutex; +use typed_arena::Arena; -use parcel_core::types::File; -use parcel_filesystem::{FileSystemRealPathCache, FileSystemRef}; - -use crate::package_json::PackageJson; -use crate::package_json::SourceField; -use crate::tsconfig::TsConfig; -use crate::tsconfig::TsConfigWrapper; -use crate::ResolverError; - -type DefaultHasher = xxhash_rust::xxh3::Xxh3Builder; +use crate::{ + fs::{FileSystem, FileSystemRealPathCache}, + package_json::{PackageJson, SourceField}, + tsconfig::{TsConfig, TsConfigWrapper}, + ResolverError, +}; pub struct Cache { - pub fs: FileSystemRef, + pub fs: Arc, + /// This stores file content strings, which are borrowed when parsing package.json and tsconfig.json files. + arena: Mutex>>, /// These map paths to parsed config files. They aren't really 'static, but Rust doens't have a good /// way to associate a lifetime with owned data stored in the same struct. We only vend temporary references /// from our public methods so this is ok for now. FrozenMap is an append only map, which doesn't require &mut /// to insert into. Since each value is in a Box, it won't move and therefore references are stable. - packages: DashMap, ResolverError>>, DefaultHasher>, - tsconfigs: DashMap, ResolverError>>, DefaultHasher>, - // In particular just the is_dir_cache spends around 8% of the time on a large project resolution - // hashing paths. Instead of using a hashmap we should try a trie here. - is_dir_cache: DashMap, - is_file_cache: DashMap, + packages: FrozenMap, ResolverError>>>, + tsconfigs: FrozenMap, ResolverError>>>, + is_file_cache: DashMap, + is_dir_cache: DashMap, realpath_cache: FileSystemRealPathCache, } -impl<'a> fmt::Debug for Cache { +impl fmt::Debug for Cache { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Cache").finish() } @@ -59,44 +59,33 @@ impl<'a> Deref for CacheCow<'a> { #[derive(Debug, Clone, PartialEq, serde::Serialize)] pub struct JsonError { - pub file: File, + pub path: PathBuf, pub line: usize, pub column: usize, pub message: String, } impl JsonError { - fn new(file: File, err: serde_json::Error) -> JsonError { + fn new(path: PathBuf, err: serde_json::Error) -> JsonError { JsonError { - file, + path, line: err.line(), column: err.column(), message: err.to_string(), } } - - fn json5( - file: File, - serde_json5::Error::Message { msg, location }: serde_json5::Error, - ) -> JsonError { - JsonError { - file, - line: location.as_ref().map(|l| l.line).unwrap_or(0), - column: location.as_ref().map(|l| l.column).unwrap_or(0), - message: msg.to_string(), - } - } } impl Cache { - pub fn new(fs: FileSystemRef) -> Self { + pub fn new(fs: Arc) -> Self { Self { fs, - packages: DashMap::with_hasher(DefaultHasher::default()), - tsconfigs: DashMap::with_hasher(DefaultHasher::default()), - is_file_cache: DashMap::with_hasher(DefaultHasher::default()), - is_dir_cache: DashMap::with_hasher(DefaultHasher::default()), - realpath_cache: FileSystemRealPathCache::default(), + arena: Mutex::new(Arena::new()), + packages: FrozenMap::new(), + tsconfigs: FrozenMap::new(), + is_file_cache: DashMap::default(), + is_dir_cache: DashMap::default(), + realpath_cache: DashMap::default(), } } @@ -124,26 +113,20 @@ impl Cache { Ok(self.fs.canonicalize(path, &self.realpath_cache)?) } - pub fn read_package(&self, path: Cow) -> Arc, ResolverError>> { + pub fn read_package<'a>(&'a self, path: Cow) -> Result<&'a PackageJson<'a>, ResolverError> { if let Some(pkg) = self.packages.get(path.as_ref()) { - return pkg.clone(); + return clone_result(pkg); } - fn read_package<'a>( - fs: &'a FileSystemRef, - realpath_cache: &'a FileSystemRealPathCache, - path: &Path, - ) -> Result { - let contents: String = fs.read_to_string(&path)?; - let mut pkg = PackageJson::parse(PathBuf::from(path), &contents).map_err(|e| { - JsonError::new( - File { - path: PathBuf::from(path), - contents: contents.into(), - }, - e, - ) - })?; + fn read_package<'fs>( + fs: &'fs dyn FileSystem, + realpath_cache: &FileSystemRealPathCache, + arena: &Mutex>>, + path: PathBuf, + ) -> Result, ResolverError> { + let contents: &str = read(fs, arena, &path)?; + let mut pkg = + PackageJson::parse(path.clone(), contents).map_err(|e| JsonError::new(path, e))?; // If the package has a `source` field, make sure // - the package is behind symlinks @@ -165,50 +148,66 @@ impl Cache { } let path = path.into_owned(); - let package: Result = - read_package(&self.fs, &self.realpath_cache, &path); - - // Since we have exclusive access to packages, - let entry = Arc::new(package.map(|pkg| Arc::new(pkg))); - let _ = self.packages.insert(path.clone(), entry.clone()); - - entry.clone() + let pkg = self.packages.insert( + path.clone(), + Box::new(read_package( + &*self.fs, + &self.realpath_cache, + &self.arena, + path, + )), + ); + + clone_result(pkg) } - pub fn read_tsconfig Result<(), ResolverError>>( - &self, + pub fn read_tsconfig<'a, F: FnOnce(&mut TsConfigWrapper<'a>) -> Result<(), ResolverError>>( + &'a self, path: &Path, process: F, - ) -> Arc, ResolverError>> { + ) -> Result<&'a TsConfigWrapper<'a>, ResolverError> { if let Some(tsconfig) = self.tsconfigs.get(path) { - return tsconfig.clone(); + return clone_result(tsconfig); } - fn read_tsconfig<'a, F: FnOnce(&mut TsConfigWrapper) -> Result<(), ResolverError>>( - fs: &FileSystemRef, + fn read_tsconfig<'fs, 'a, F: FnOnce(&mut TsConfigWrapper<'a>) -> Result<(), ResolverError>>( + fs: &'fs dyn FileSystem, + arena: &Mutex>>, path: &Path, process: F, - ) -> Result { - let data = fs.read_to_string(path)?; - let mut tsconfig = TsConfig::parse(path.to_owned(), &data).map_err(|e| { - JsonError::json5( - File { - contents: data, - path: path.to_owned(), - }, - e, - ) - })?; - process(&mut tsconfig)?; + ) -> Result, ResolverError> { + let data = read(fs, arena, path)?; + let mut tsconfig = + TsConfig::parse(path.to_owned(), data).map_err(|e| JsonError::new(path.to_owned(), e))?; + // Convice the borrow checker that 'a will live as long as self and not 'static. + // Since the data is in our arena, this is true. + process(unsafe { std::mem::transmute(&mut tsconfig) })?; Ok(tsconfig) } - // Since we have exclusive access to tsconfigs, it should be impossible for the get to fail - // after insert - let tsconfig = read_tsconfig(&self.fs, path, process).map(|t| Arc::new(t)); - let tsconfig = Arc::new(tsconfig); - let _ = self.tsconfigs.insert(PathBuf::from(path), tsconfig.clone()); + let tsconfig = self.tsconfigs.insert( + path.to_owned(), + Box::new(read_tsconfig(&*self.fs, &self.arena, path, process)), + ); + + clone_result(tsconfig) + } +} + +fn read<'fs>( + fs: &'fs dyn FileSystem, + arena: &Mutex>>, + path: &Path, +) -> std::io::Result<&'static mut str> { + let arena = arena.lock(); + let data = arena.alloc(fs.read_to_string(path)?.into_boxed_str()); + // The data lives as long as the arena. In public methods, we only vend temporary references. + Ok(unsafe { &mut *(&mut **data as *mut str) }) +} - tsconfig +fn clone_result(res: &Result) -> Result<&T, E> { + match res { + Ok(v) => Ok(v), + Err(err) => Err(err.clone()), } } diff --git a/packages/utils/node-resolver-rs/src/error.rs b/packages/utils/node-resolver-rs/src/error.rs index bae446bc709..2d1439699cc 100644 --- a/packages/utils/node-resolver-rs/src/error.rs +++ b/packages/utils/node-resolver-rs/src/error.rs @@ -1,52 +1,44 @@ -use std::fmt::{Display, Formatter}; +use crate::PackageJsonError; +use crate::{cache::JsonError, specifier::SpecifierError}; use std::path::PathBuf; use std::sync::Arc; -use thiserror::Error; - -use crate::cache::JsonError; -use crate::specifier::SpecifierError; -use crate::PackageJsonError; - -#[derive(Debug, Clone, PartialEq, serde::Serialize, Error)] +#[derive(Debug, Clone, PartialEq, serde::Serialize)] #[serde(tag = "type")] pub enum ResolverError { - #[error("Unknown scheme {scheme}")] - UnknownScheme { scheme: String }, - #[error("Unknown error")] + UnknownScheme { + scheme: String, + }, UnknownError, - #[error("File {relative} not found from {from}")] - FileNotFound { relative: PathBuf, from: PathBuf }, - #[error("Module {module} not found")] - ModuleNotFound { module: String }, - #[error("Module {module} entry not found in path {entry_path} with package {package_path} and field {field}")] + FileNotFound { + relative: PathBuf, + from: PathBuf, + }, + ModuleNotFound { + module: String, + }, ModuleEntryNotFound { module: String, entry_path: PathBuf, package_path: PathBuf, field: &'static str, }, - #[error("Module {module} subpath {path} with package {package_path}")] ModuleSubpathNotFound { module: String, path: PathBuf, package_path: PathBuf, }, - #[error("JSON error")] JsonError(JsonError), - #[error("IO error")] IOError(IOError), - #[error("Package JSON error. Module {module} at path {path}")] PackageJsonError { - error: PackageJsonError, module: String, path: PathBuf, + error: PackageJsonError, + }, + PackageJsonNotFound { + from: PathBuf, }, - #[error("Package JSON not found from {from}")] - PackageJsonNotFound { from: PathBuf }, - #[error("Invalid specifier")] InvalidSpecifier(SpecifierError), - #[error("TS config extends not found for {tsconfig}. Error {error}")] TsConfigExtendsNotFound { tsconfig: PathBuf, error: Box, @@ -74,12 +66,6 @@ impl serde::Serialize for IOError { } } -impl Display for IOError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - impl PartialEq for IOError { fn eq(&self, other: &Self) -> bool { self.0.kind() == other.0.kind() diff --git a/packages/utils/node-resolver-rs/src/fs.rs b/packages/utils/node-resolver-rs/src/fs.rs new file mode 100644 index 00000000000..0ca4b02e9c6 --- /dev/null +++ b/packages/utils/node-resolver-rs/src/fs.rs @@ -0,0 +1,41 @@ +use std::{ + io::Result, + path::{Path, PathBuf}, +}; + +#[cfg(not(target_arch = "wasm32"))] +use crate::path::canonicalize; +use dashmap::DashMap; + +pub trait FileSystem: Send + Sync { + fn canonicalize(&self, path: &Path, cache: &FileSystemRealPathCache) -> Result; + fn read_to_string(&self, path: &Path) -> Result; + fn is_file(&self, path: &Path) -> bool; + fn is_dir(&self, path: &Path) -> bool; +} + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Default)] +pub struct OsFileSystem; + +pub type FileSystemRealPathCache = + DashMap, xxhash_rust::xxh3::Xxh3Builder>; + +#[cfg(not(target_arch = "wasm32"))] +impl FileSystem for OsFileSystem { + fn canonicalize(&self, path: &Path, cache: &FileSystemRealPathCache) -> Result { + canonicalize(path, cache) + } + + fn read_to_string(&self, path: &Path) -> Result { + std::fs::read_to_string(path) + } + + fn is_file(&self, path: &Path) -> bool { + path.is_file() + } + + fn is_dir(&self, path: &Path) -> bool { + path.is_dir() + } +} diff --git a/packages/utils/node-resolver-rs/src/invalidations.rs b/packages/utils/node-resolver-rs/src/invalidations.rs index 0b7c9996c15..e00e5540c04 100644 --- a/packages/utils/node-resolver-rs/src/invalidations.rs +++ b/packages/utils/node-resolver-rs/src/invalidations.rs @@ -1,12 +1,11 @@ -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::RwLock; +use std::{ + path::{Path, PathBuf}, + sync::atomic::{AtomicBool, Ordering}, +}; -use crate::path::normalize_path; -use crate::ResolverError; +use dashmap::DashSet; + +use crate::{path::normalize_path, ResolverError}; #[derive(PartialEq, Eq, Hash, Debug, Clone)] pub enum FileCreateInvalidation { @@ -17,9 +16,8 @@ pub enum FileCreateInvalidation { #[derive(Default, Debug)] pub struct Invalidations { - pub invalidate_on_file_create: - RwLock>, - pub invalidate_on_file_change: RwLock>, + pub invalidate_on_file_create: DashSet, + pub invalidate_on_file_change: DashSet, pub invalidate_on_startup: AtomicBool, } @@ -27,16 +25,12 @@ impl Invalidations { pub fn invalidate_on_file_create(&self, path: &Path) { self .invalidate_on_file_create - .write() - .unwrap() .insert(FileCreateInvalidation::Path(normalize_path(path))); } pub fn invalidate_on_file_create_above>(&self, file_name: S, above: &Path) { self .invalidate_on_file_create - .write() - .unwrap() .insert(FileCreateInvalidation::FileName { file_name: file_name.into(), above: normalize_path(above), @@ -46,16 +40,12 @@ impl Invalidations { pub fn invalidate_on_glob_create>(&self, glob: S) { self .invalidate_on_file_create - .write() - .unwrap() .insert(FileCreateInvalidation::Glob(glob.into())); } pub fn invalidate_on_file_change(&self, invalidation: &Path) { self .invalidate_on_file_change - .write() - .unwrap() .insert(normalize_path(invalidation)); } @@ -64,14 +54,12 @@ impl Invalidations { } pub fn extend(&self, other: &Invalidations) { - let mut invalidate_on_file_create = self.invalidate_on_file_create.write().unwrap(); - for f in other.invalidate_on_file_create.read().unwrap().iter() { - invalidate_on_file_create.insert(f.clone()); + for f in other.invalidate_on_file_create.iter() { + self.invalidate_on_file_create.insert(f.clone()); } - let mut invalidate_on_file_change = self.invalidate_on_file_change.write().unwrap(); - for f in other.invalidate_on_file_change.read().unwrap().iter() { - invalidate_on_file_change.insert(f.clone()); + for f in other.invalidate_on_file_change.iter() { + self.invalidate_on_file_change.insert(f.clone()); } if other.invalidate_on_startup.load(Ordering::Relaxed) { diff --git a/packages/utils/node-resolver-rs/src/lib.rs b/packages/utils/node-resolver-rs/src/lib.rs index 2364e1fd5a8..fa3f4388bc9 100644 --- a/packages/utils/node-resolver-rs/src/lib.rs +++ b/packages/utils/node-resolver-rs/src/lib.rs @@ -1,32 +1,24 @@ -use std::borrow::Cow; -use std::ops::Deref; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; +use std::{ + borrow::Cow, + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; use bitflags::bitflags; use once_cell::unsync::OnceCell; -pub use cache::Cache; -pub use cache::CacheCow; +pub use cache::{Cache, CacheCow}; pub use error::ResolverError; -pub use invalidations::*; -use package_json::AliasValue; -pub use package_json::ExportsCondition; -use package_json::ExportsResolution; -pub use package_json::Fields; -pub use package_json::ModuleType; -use package_json::PackageJson; -pub use package_json::PackageJsonError; -pub use parcel_core::types::IncludeNodeModules; #[cfg(not(target_arch = "wasm32"))] -pub use parcel_filesystem::os_file_system::OsFileSystem; -pub use parcel_filesystem::FileSystem; -pub use specifier::parse_package_specifier; -pub use specifier::parse_scheme; -pub use specifier::Specifier; -pub use specifier::SpecifierError; -pub use specifier::SpecifierType; +pub use fs::OsFileSystem; +pub use fs::{FileSystem, FileSystemRealPathCache}; +pub use invalidations::*; +use package_json::{AliasValue, ExportsResolution, PackageJson}; +pub use package_json::{ExportsCondition, Fields, ModuleType, PackageJsonError}; +pub use specifier::{ + parse_package_specifier, parse_scheme, Specifier, SpecifierError, SpecifierType, +}; use tsconfig::TsConfig; use crate::path::resolve_path; @@ -34,6 +26,7 @@ use crate::path::resolve_path; mod builtins; mod cache; mod error; +mod fs; mod invalidations; mod package_json; mod path; @@ -76,6 +69,19 @@ bitflags! { } } +#[derive(Clone)] +pub enum IncludeNodeModules { + Bool(bool), + Array(Vec), + Map(HashMap), +} + +impl Default for IncludeNodeModules { + fn default() -> Self { + IncludeNodeModules::Bool(true) + } +} + type ResolveModuleDir = dyn Fn(&str, &Path) -> Result + Send + Sync; pub struct Resolver<'a> { @@ -189,7 +195,6 @@ impl<'a> Resolver<'a> { specifier_type: SpecifierType, options: ResolveOptions, ) -> ResolveResult { - tracing::trace!(%specifier, ?from, ?specifier_type, "Resolving specifier"); let invalidations = Invalidations::default(); let result = self.resolve_with_invalidations(specifier, from, specifier_type, &invalidations, options); @@ -269,9 +274,9 @@ impl<'a> Resolver<'a> { &self, from: &Path, invalidations: &Invalidations, - ) -> Result>, ResolverError> { + ) -> Result, ResolverError> { if let Some(path) = self.find_ancestor_file(from, "package.json", invalidations) { - let package = self.cache.read_package(Cow::Owned(path)).deref().clone()?; + let package = self.cache.read_package(Cow::Owned(path))?; return Ok(Some(package)); } @@ -315,12 +320,12 @@ impl<'a> Resolver<'a> { struct ResolveRequest<'a> { resolver: &'a Resolver<'a>, - specifier: &'a Specifier, + specifier: &'a Specifier<'a>, specifier_type: SpecifierType, from: &'a Path, flags: RequestFlags, - tsconfig: OnceCell>, - root_package: OnceCell>>, + tsconfig: OnceCell>>, + root_package: OnceCell>>, invalidations: &'a Invalidations, conditions: ExportsCondition, custom_conditions: &'a [String], @@ -338,7 +343,7 @@ bitflags! { impl<'a> ResolveRequest<'a> { fn new( resolver: &'a Resolver<'a>, - specifier: &'a Specifier, + specifier: &'a Specifier<'a>, mut specifier_type: SpecifierType, from: &'a Path, invalidations: &'a Invalidations, @@ -432,11 +437,10 @@ impl<'a> ResolveRequest<'a> { } } - fn root_package(&self) -> Result>, ResolverError> { + fn root_package(&self) -> Result<&Option<&PackageJson>, ResolverError> { self .root_package .get_or_try_init(|| self.find_package(&self.resolver.project_root)) - .cloned() } fn resolve(&self) -> Result { @@ -467,7 +471,7 @@ impl<'a> ResolveRequest<'a> { Ok(res) } else { Err(ResolverError::FileNotFound { - relative: specifier.to_owned(), + relative: specifier.as_ref().to_owned(), from: PathBuf::from("/"), }) } @@ -520,7 +524,7 @@ impl<'a> ResolveRequest<'a> { if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(self.specifier)? { return Ok(res); } - Ok(Resolution::Builtin(builtin.to_owned())) + Ok(Resolution::Builtin(builtin.as_ref().to_owned())) } Specifier::Url(url) => { if self.specifier_type == SpecifierType::Url { @@ -543,7 +547,7 @@ impl<'a> ResolveRequest<'a> { .find_ancestor_file(from, filename, self.invalidations) } - fn find_package(&self, from: &Path) -> Result>, ResolverError> { + fn find_package(&self, from: &Path) -> Result>, ResolverError> { self.resolver.find_package(from, self.invalidations) } @@ -556,7 +560,7 @@ impl<'a> ResolveRequest<'a> { None }; - if let Some(res) = self.load_path(&path, package.as_deref())? { + if let Some(res) = self.load_path(&path, package)? { return Ok(res); } @@ -578,7 +582,7 @@ impl<'a> ResolveRequest<'a> { } // Try aliases and tsconfig paths first. - let specifier = Specifier::Package(String::from(module), String::from(subpath)); + let specifier = Specifier::Package(Cow::Borrowed(module), Cow::Borrowed(subpath)); if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(&specifier)? { return Ok(res); } @@ -593,7 +597,7 @@ impl<'a> ResolveRequest<'a> { if self.resolver.flags.contains(Flags::ALIASES) { // First, check for an alias in the root package.json. if let Some(package) = self.root_package()? { - if let Some(res) = self.resolve_aliases(&package, specifier, Fields::ALIAS)? { + if let Some(res) = self.resolve_aliases(package, specifier, Fields::ALIAS)? { return Ok(Some(res)); } } @@ -604,7 +608,7 @@ impl<'a> ResolveRequest<'a> { if self.resolver.entries.contains(Fields::BROWSER) { fields |= Fields::BROWSER; } - if let Some(res) = self.resolve_aliases(&package, specifier, fields)? { + if let Some(res) = self.resolve_aliases(package, specifier, fields)? { return Ok(Some(res)); } } @@ -659,13 +663,11 @@ impl<'a> ResolveRequest<'a> { .resolver .cache .read_package(Cow::Borrowed(&package_path)) - .deref() - .clone() }); let package = match package { Ok(package) => package, - Err(ResolverError::IOError(_)) | Err(ResolverError::FileNotFound { .. }) => { + Err(ResolverError::IOError(_)) => { // No package.json in node_modules is probably invalid but we have tests for it... if self.resolver.flags.contains(Flags::DIR_INDEX) { if let Some(res) = self.load_file(&package_dir.join(self.resolver.index_file), None)? { @@ -683,7 +685,7 @@ impl<'a> ResolveRequest<'a> { // Try the "source" field first, if present. if self.resolver.entries.contains(Fields::SOURCE) && subpath.is_empty() { if let Some(source) = package.source() { - if let Some(res) = self.load_path(&source, Some(&*package))? { + if let Some(res) = self.load_path(&source, Some(package))? { return Ok(res); } } @@ -708,7 +710,7 @@ impl<'a> ResolveRequest<'a> { .flags .contains(Flags::EXPORTS_OPTIONAL_EXTENSIONS) { - if let Some(res) = self.load_file(&path, Some(&*package))? { + if let Some(res) = self.load_file(&path, Some(package))? { return Ok(res); } } else if let Some(res) = self.try_file_without_aliases(&path)? { @@ -723,7 +725,7 @@ impl<'a> ResolveRequest<'a> { }) } else if !subpath.is_empty() { package_dir.push(subpath); - if let Some(res) = self.load_path(&package_dir, Some(&*package))? { + if let Some(res) = self.load_path(&package_dir, Some(package))? { return Ok(res); } @@ -733,7 +735,7 @@ impl<'a> ResolveRequest<'a> { package_path: package.path.clone(), }) } else { - let res = self.try_package_entries(&*package); + let res = self.try_package_entries(package); if let Ok(Some(res)) = res { return Ok(res); } @@ -741,7 +743,7 @@ impl<'a> ResolveRequest<'a> { // Node ESM doesn't allow directory imports. if self.resolver.flags.contains(Flags::DIR_INDEX) { if let Some(res) = - self.load_file(&package_dir.join(self.resolver.index_file), Some(&*package))? + self.load_file(&package_dir.join(self.resolver.index_file), Some(package))? { return Ok(res); } @@ -943,13 +945,10 @@ impl<'a> ResolveRequest<'a> { ) -> Result, ResolverError> { // TypeScript supports a moduleSuffixes option in tsconfig.json which allows suffixes // such as ".ios" to be appended just before the last extension. - let empty_string = String::from(""); - let empty_string = [empty_string]; - let tsconfig = self.tsconfig()?; - let module_suffixes = tsconfig - .as_ref() + let module_suffixes = self + .tsconfig()? .and_then(|tsconfig| tsconfig.module_suffixes.as_ref()) - .map_or(empty_string.as_slice(), |v| v.as_slice()); + .map_or([""].as_slice(), |v| v.as_slice()); for suffix in module_suffixes { let mut p = if !suffix.is_empty() { @@ -1006,8 +1005,8 @@ impl<'a> ResolveRequest<'a> { // Check the project root package.json first. if let Some(package) = self.root_package()? { if let Ok(s) = path.strip_prefix(package.path.parent().unwrap()) { - let specifier = Specifier::Relative(s.to_path_buf()); - if let Some(res) = self.resolve_aliases(&package, &specifier, Fields::ALIAS)? { + let specifier = Specifier::Relative(Cow::Borrowed(s)); + if let Some(res) = self.resolve_aliases(package, &specifier, Fields::ALIAS)? { return Ok(Some(res)); } } @@ -1016,7 +1015,7 @@ impl<'a> ResolveRequest<'a> { // Next try the local package.json. if let Some(package) = package { if let Ok(s) = path.strip_prefix(package.path.parent().unwrap()) { - let specifier = Specifier::Relative(s.to_path_buf()); + let specifier = Specifier::Relative(Cow::Borrowed(s)); let mut fields = Fields::ALIAS; if self.resolver.entries.contains(Fields::BROWSER) { fields |= Fields::BROWSER; @@ -1056,14 +1055,9 @@ impl<'a> ResolveRequest<'a> { let path = dir.join("package.json"); let mut res = Ok(None); let package = if let Ok(package) = self.invalidations.read(&path, || { - self - .resolver - .cache - .read_package(Cow::Borrowed(&path)) - .deref() - .clone() + self.resolver.cache.read_package(Cow::Borrowed(&path)) }) { - res = self.try_package_entries(&package); + res = self.try_package_entries(package); if matches!(res, Ok(Some(_))) { return res; } @@ -1074,8 +1068,10 @@ impl<'a> ResolveRequest<'a> { // If no package.json, or no entries, try an index file with all possible extensions. if self.resolver.flags.contains(Flags::DIR_INDEX) && self.resolver.cache.is_dir(dir) { - let target = package.as_deref().or(parent_package); - return self.load_file(&dir.join(self.resolver.index_file), target); + return self.load_file( + &dir.join(self.resolver.index_file), + package.or(parent_package), + ); } res @@ -1094,7 +1090,7 @@ impl<'a> ResolveRequest<'a> { Ok(None) } - fn tsconfig(&self) -> Result<&Option, ResolverError> { + fn tsconfig(&self) -> Result<&Option<&TsConfig>, ResolverError> { if self.resolver.flags.contains(Flags::TSCONFIG) && self .flags @@ -1114,12 +1110,12 @@ impl<'a> ResolveRequest<'a> { } } - fn read_tsconfig(&self, path: PathBuf) -> Result { + fn read_tsconfig(&self, path: PathBuf) -> Result<&'a TsConfig<'a>, ResolverError> { let tsconfig = self.invalidations.read(&path, || { - let tsconfig = self.resolver.cache.read_tsconfig(&path, |tsconfig| { + self.resolver.cache.read_tsconfig(&path, |tsconfig| { for i in 0..tsconfig.extends.len() { let path = match &tsconfig.extends[i] { - Specifier::Absolute(path) => path.to_owned(), + Specifier::Absolute(path) => path.as_ref().to_owned(), Specifier::Relative(path) => { let mut absolute_path = resolve_path(&tsconfig.compiler_options.path, path); @@ -1198,15 +1194,14 @@ impl<'a> ResolveRequest<'a> { }; let extended = self.read_tsconfig(path)?; - tsconfig.compiler_options.extend(&extended); + tsconfig.compiler_options.extend(extended); } Ok(()) - }); - tsconfig.deref().clone() + }) })?; - Ok(tsconfig.compiler_options.clone()) + Ok(&tsconfig.compiler_options) } } @@ -1336,19 +1331,14 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() + .into_iter() .collect::>(), HashSet::new() ); assert_eq!( invalidations .invalidate_on_file_change - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) ); @@ -1591,10 +1581,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([FileCreateInvalidation::FileName { file_name: "node_modules/foo".into(), @@ -1604,10 +1591,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_change - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([ root().join("node_modules/foo/package.json"), @@ -1744,10 +1728,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([FileCreateInvalidation::FileName { file_name: "node_modules/package-alias".into(), @@ -1757,10 +1738,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_change - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([ root().join("node_modules/package-alias/package.json"), @@ -2476,20 +2454,14 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::new() ); assert_eq!( invalidations .invalidate_on_file_change - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) ); @@ -2689,10 +2661,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_create - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([ FileCreateInvalidation::Path(root().join("ts-extensions/a.js")), @@ -2709,10 +2678,7 @@ mod tests { assert_eq!( invalidations .invalidate_on_file_change - .read() - .unwrap() - .iter() - .cloned() + .into_iter() .collect::>(), HashSet::from([root().join("package.json"), root().join("tsconfig.json")]) ); diff --git a/packages/utils/node-resolver-rs/src/package_json.rs b/packages/utils/node-resolver-rs/src/package_json.rs index be83eb7889e..d88255e17d1 100644 --- a/packages/utils/node-resolver-rs/src/package_json.rs +++ b/packages/utils/node-resolver-rs/src/package_json.rs @@ -1,22 +1,19 @@ -use std::borrow::Cow; -use std::cmp::Ordering; -use std::ops::Range; -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; +use std::{ + borrow::Cow, + cmp::Ordering, + ops::Range, + path::{Component, Path, PathBuf}, +}; use bitflags::bitflags; -use glob_match::glob_match; -use glob_match::glob_match_with_captures; +use glob_match::{glob_match, glob_match_with_captures}; use indexmap::IndexMap; use serde::Deserialize; -pub use parcel_core::types::ExportsCondition; - -use crate::path::resolve_path; -use crate::specifier::decode_path; -use crate::specifier::Specifier; -use crate::specifier::SpecifierType; +use crate::{ + path::resolve_path, + specifier::{decode_path, Specifier, SpecifierType}, +}; bitflags! { #[derive(serde::Serialize)] @@ -33,29 +30,29 @@ bitflags! { #[derive(serde::Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] -pub struct PackageJson { +pub struct PackageJson<'a> { #[serde(skip)] pub path: PathBuf, #[serde(default)] - pub name: String, + pub name: &'a str, #[serde(rename = "type", default)] pub module_type: ModuleType, - main: Option, - module: Option, - tsconfig: Option, - types: Option, + main: Option<&'a str>, + module: Option<&'a str>, + tsconfig: Option<&'a str>, + types: Option<&'a str>, #[serde(default)] - pub source: SourceField, + pub source: SourceField<'a>, #[serde(default)] - browser: BrowserField, + browser: BrowserField<'a>, #[serde(default)] - alias: IndexMap, + alias: IndexMap, AliasValue<'a>>, #[serde(default)] - exports: ExportsField, + exports: ExportsField<'a>, #[serde(default)] - imports: IndexMap, + imports: IndexMap, ExportsField<'a>>, #[serde(default)] - side_effects: SideEffects, + side_effects: SideEffects<'a>, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Default, PartialEq)] @@ -70,59 +67,114 @@ pub enum ModuleType { #[derive(serde::Deserialize, Debug, Default)] #[serde(untagged)] -pub enum BrowserField { +pub enum BrowserField<'a> { #[default] None, - String(String), - Map(IndexMap), + #[serde(borrow)] + String(&'a str), + Map(IndexMap, AliasValue<'a>>), } #[derive(serde::Deserialize, Debug, Default)] #[serde(untagged)] -pub enum SourceField { +pub enum SourceField<'a> { #[default] None, - String(String), - Map(IndexMap), - Array(Vec), + #[serde(borrow)] + String(&'a str), + Map(IndexMap, AliasValue<'a>>), + Array(Vec<&'a str>), Bool(bool), } #[derive(serde::Deserialize, Debug, Default, PartialEq)] #[serde(untagged)] -pub enum ExportsField { +pub enum ExportsField<'a> { #[default] None, - String(String), - Array(Vec), - Map(IndexMap), + #[serde(borrow)] + String(&'a str), + Array(Vec>), + Map(IndexMap, ExportsField<'a>>), +} + +bitflags! { + pub struct ExportsCondition: u16 { + const IMPORT = 1 << 0; + const REQUIRE = 1 << 1; + const MODULE = 1 << 2; + const NODE = 1 << 3; + const BROWSER = 1 << 4; + const WORKER = 1 << 5; + const WORKLET = 1 << 6; + const ELECTRON = 1 << 7; + const DEVELOPMENT = 1 << 8; + const PRODUCTION = 1 << 9; + const TYPES = 1 << 10; + const DEFAULT = 1 << 11; + const STYLE = 1 << 12; + const SASS = 1 << 13; + const LESS = 1 << 14; + const STYLUS = 1 << 15; + } +} + +impl Default for ExportsCondition { + fn default() -> Self { + ExportsCondition::empty() + } +} + +impl TryFrom<&str> for ExportsCondition { + type Error = (); + fn try_from(value: &str) -> Result { + Ok(match value { + "import" => ExportsCondition::IMPORT, + "require" => ExportsCondition::REQUIRE, + "module" => ExportsCondition::MODULE, + "node" => ExportsCondition::NODE, + "browser" => ExportsCondition::BROWSER, + "worker" => ExportsCondition::WORKER, + "worklet" => ExportsCondition::WORKLET, + "electron" => ExportsCondition::ELECTRON, + "development" => ExportsCondition::DEVELOPMENT, + "production" => ExportsCondition::PRODUCTION, + "types" => ExportsCondition::TYPES, + "default" => ExportsCondition::DEFAULT, + "style" => ExportsCondition::STYLE, + "sass" => ExportsCondition::SASS, + "less" => ExportsCondition::LESS, + "stylus" => ExportsCondition::STYLUS, + _ => return Err(()), + }) + } } #[derive(Debug, PartialEq, Eq, Hash)] -pub enum ExportsKey { +pub enum ExportsKey<'a> { Main, - Pattern(String), + Pattern(&'a str), Condition(ExportsCondition), - CustomCondition(String), + CustomCondition(&'a str), } -impl<'a> From<&'a str> for ExportsKey { +impl<'a> From<&'a str> for ExportsKey<'a> { fn from(key: &'a str) -> Self { if key == "." { ExportsKey::Main } else if let Some(key) = key.strip_prefix("./") { - ExportsKey::Pattern(key.to_string()) + ExportsKey::Pattern(key) } else if let Some(key) = key.strip_prefix('#') { - ExportsKey::Pattern(key.to_string()) + ExportsKey::Pattern(key) } else if let Ok(c) = ExportsCondition::try_from(key) { ExportsKey::Condition(c) } else { - ExportsKey::CustomCondition(key.to_string()) + ExportsKey::CustomCondition(key) } } } -impl<'de> Deserialize<'de> for ExportsKey { +impl<'a, 'de: 'a> Deserialize<'de> for ExportsKey<'a> { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -134,20 +186,24 @@ impl<'de> Deserialize<'de> for ExportsKey { #[derive(serde::Deserialize, Clone, PartialEq, Debug)] #[serde(untagged)] -pub enum AliasValue { - Specifier(Specifier), +pub enum AliasValue<'a> { + #[serde(borrow)] + Specifier(Specifier<'a>), Bool(bool), - Global { global: String }, + Global { + global: &'a str, + }, } #[derive(serde::Deserialize, Clone, Default, PartialEq, Debug)] #[serde(untagged)] -pub enum SideEffects { +pub enum SideEffects<'a> { #[default] None, Boolean(bool), - String(String), - Array(Vec), + #[serde(borrow)] + String(&'a str), + Array(Vec<&'a str>), } #[derive(Debug, Clone, PartialEq, serde::Serialize)] @@ -165,8 +221,8 @@ pub enum ExportsResolution<'a> { Package(Cow<'a, str>), } -impl PackageJson { - pub fn parse(path: PathBuf, data: &str) -> serde_json::Result { +impl<'a> PackageJson<'a> { + pub fn parse(path: PathBuf, data: &'a str) -> serde_json::Result> { let mut parsed: PackageJson = serde_json::from_str(data)?; parsed.path = path; Ok(parsed) @@ -183,12 +239,13 @@ impl PackageJson { match &self.source { SourceField::None | SourceField::Array(_) | SourceField::Bool(_) => None, SourceField::String(source) => Some(resolve_path(&self.path, source)), - SourceField::Map(map) => { - match map.get(&Specifier::Package(self.name.clone(), String::from(""))) { - Some(AliasValue::Specifier(Specifier::Relative(s))) => Some(resolve_path(&self.path, s)), - _ => None, - } - } + SourceField::Map(map) => match map.get(&Specifier::Package( + Cow::Borrowed(self.name), + Cow::Borrowed(""), + )) { + Some(AliasValue::Specifier(Specifier::Relative(s))) => Some(resolve_path(&self.path, s)), + _ => None, + }, } } @@ -198,7 +255,7 @@ impl PackageJson { pub fn resolve_package_exports( &self, - subpath: &str, + subpath: &'a str, conditions: ExportsCondition, custom_conditions: &[String], ) -> Result { @@ -259,7 +316,7 @@ impl PackageJson { pub fn resolve_package_imports( &self, - specifier: &str, + specifier: &'a str, conditions: ExportsCondition, custom_conditions: &[String], ) -> Result, PackageJsonError> { @@ -283,7 +340,7 @@ impl PackageJson { fn resolve_package_target( &self, - target: &ExportsField, + target: &'a ExportsField, pattern_match: &str, is_imports: bool, conditions: ExportsCondition, @@ -301,11 +358,11 @@ impl PackageJson { return Ok(ExportsResolution::Package(Cow::Owned(target))); } - return Ok(ExportsResolution::Package(Cow::Owned(target.clone()))); + return Ok(ExportsResolution::Package(Cow::Borrowed(target))); } let target = if pattern_match.is_empty() { - Cow::Borrowed(target) + Cow::Borrowed(*target) } else { Cow::Owned(target.replace('*', pattern_match)) }; @@ -380,13 +437,13 @@ impl PackageJson { fn resolve_package_imports_exports( &self, - match_key: &str, - match_obj: &IndexMap, + match_key: &'a str, + match_obj: &'a IndexMap, ExportsField<'a>>, is_imports: bool, conditions: ExportsCondition, custom_conditions: &[String], ) -> Result, PackageJsonError> { - let pattern = ExportsKey::Pattern(match_key.to_string()); + let pattern = ExportsKey::Pattern(match_key); if let Some(target) = match_obj.get(&pattern) { if !match_key.contains('*') { return self.resolve_package_target(target, "", is_imports, conditions, custom_conditions); @@ -413,7 +470,7 @@ impl PackageJson { if !best_key.is_empty() { return self.resolve_package_target( - &match_obj[&ExportsKey::Pattern(best_key.to_string())], + &match_obj[&ExportsKey::Pattern(best_key)], best_match, is_imports, conditions, @@ -424,11 +481,11 @@ impl PackageJson { Ok(ExportsResolution::None) } - pub fn resolve_aliases<'b>( - &'b self, - specifier: &'b Specifier, + pub fn resolve_aliases( + &self, + specifier: &Specifier<'a>, fields: Fields, - ) -> Option> { + ) -> Option> { if fields.contains(Fields::SOURCE) { if let SourceField::Map(source) = &self.source { match self.resolve_alias(source, specifier) { @@ -457,18 +514,18 @@ impl PackageJson { None } - fn resolve_alias<'b>( - &'b self, - map: &'b IndexMap, - specifier: &'b Specifier, - ) -> Option> { + fn resolve_alias( + &self, + map: &'a IndexMap, AliasValue<'a>>, + specifier: &Specifier<'a>, + ) -> Option> { if let Some(alias) = self.lookup_alias(map, specifier) { return Some(alias); } if let Specifier::Package(package, subpath) = specifier { if let Some(alias) = - self.lookup_alias(map, &Specifier::Package(package.clone(), String::from(""))) + self.lookup_alias(map, &Specifier::Package(package.clone(), Cow::Borrowed(""))) { match alias.as_ref() { AliasValue::Specifier(base) => { @@ -476,7 +533,7 @@ impl PackageJson { match base { Specifier::Package(base_pkg, base_subpath) => { let subpath = if !base_subpath.is_empty() && !subpath.is_empty() { - format!("{}/{}", base_subpath, subpath) + Cow::Owned(format!("{}/{}", base_subpath, subpath)) } else if !subpath.is_empty() { subpath.clone() } else { @@ -492,7 +549,7 @@ impl PackageJson { return Some(alias); } else { return Some(Cow::Owned(AliasValue::Specifier(Specifier::Relative( - path.join(&subpath), + Cow::Owned(path.join(subpath.as_ref())), )))); } } @@ -501,7 +558,7 @@ impl PackageJson { return Some(alias); } else { return Some(Cow::Owned(AliasValue::Specifier(Specifier::Absolute( - path.join(subpath), + Cow::Owned(path.join(subpath.as_ref())), )))); } } @@ -510,7 +567,7 @@ impl PackageJson { return Some(alias); } else { return Some(Cow::Owned(AliasValue::Specifier(Specifier::Tilde( - path.join(subpath), + Cow::Owned(path.join(subpath.as_ref())), )))); } } @@ -525,11 +582,11 @@ impl PackageJson { None } - fn lookup_alias<'b>( + fn lookup_alias( &self, - map: &'b IndexMap, - specifier: &Specifier, - ) -> Option> { + map: &'a IndexMap, AliasValue<'a>>, + specifier: &Specifier<'a>, + ) -> Option> { if let Some(value) = map.get(specifier) { return Some(Cow::Borrowed(value)); } @@ -565,10 +622,9 @@ impl PackageJson { Specifier::Absolute(replace_path_captures(r, &path, &captures)?) } Specifier::Tilde(r) => Specifier::Tilde(replace_path_captures(r, &path, &captures)?), - Specifier::Package(module, subpath) => Specifier::Package( - module.clone(), - replace_captures(subpath, &path, &captures).to_string(), - ), + Specifier::Package(module, subpath) => { + Specifier::Package(module.clone(), replace_captures(subpath, &path, &captures)) + } _ => return Some(Cow::Borrowed(value)), }), _ => return Some(Cow::Borrowed(value)), @@ -621,18 +677,19 @@ fn replace_path_captures<'a>( s: &'a Path, path: &str, captures: &Vec>, -) -> Option { - Some(PathBuf::from(replace_captures( - s.as_os_str().to_str()?, - path, - captures, - ))) +) -> Option> { + Some( + match replace_captures(s.as_os_str().to_str()?, path, captures) { + Cow::Borrowed(b) => Cow::Borrowed(Path::new(b)), + Cow::Owned(b) => Cow::Owned(PathBuf::from(b)), + }, + ) } /// Inserts captures matched in a glob against `path` using a pattern string. /// Replacements are inserted using JS-like $N syntax, e.g. $1 for the first capture. -fn replace_captures<'a>(s: &'a str, path: &str, captures: &Vec>) -> String { - let mut res = s.to_string(); +fn replace_captures<'a>(s: &'a str, path: &str, captures: &Vec>) -> Cow<'a, str> { + let mut res = Cow::Borrowed(s); let bytes = s.as_bytes(); for (idx, _) in s.match_indices('$').rev() { let mut end = idx; @@ -643,7 +700,9 @@ fn replace_captures<'a>(s: &'a str, path: &str, captures: &Vec>) -> if end != idx { if let Ok(capture_index) = s[idx + 1..end + 1].parse::() { if capture_index > 0 && capture_index - 1 < captures.len() { - res.replace_range(idx..end + 1, &path[captures[capture_index - 1].clone()]); + res + .to_mut() + .replace_range(idx..end + 1, &path[captures[capture_index - 1].clone()]); } } } @@ -674,7 +733,7 @@ fn pattern_key_compare(a: &str, b: &str) -> Ordering { } pub struct EntryIter<'a> { - package: &'a PackageJson, + package: &'a PackageJson<'a>, fields: Fields, } @@ -691,7 +750,7 @@ impl<'a> Iterator for EntryIter<'a> { if self.fields.contains(Fields::TYPES) { self.fields.remove(Fields::TYPES); - if let Some(types) = &self.package.types { + if let Some(types) = self.package.types { return Some((resolve_path(&self.package.path, types), "types")); } } @@ -705,8 +764,8 @@ impl<'a> Iterator for EntryIter<'a> { } BrowserField::Map(map) => { if let Some(AliasValue::Specifier(Specifier::Relative(s))) = map.get(&Specifier::Package( - self.package.name.clone(), - String::from(""), + Cow::Borrowed(self.package.name), + Cow::Borrowed(""), )) { return Some((resolve_path(&self.package.path, s), "browser")); } @@ -716,21 +775,21 @@ impl<'a> Iterator for EntryIter<'a> { if self.fields.contains(Fields::MODULE) { self.fields.remove(Fields::MODULE); - if let Some(module) = &self.package.module { + if let Some(module) = self.package.module { return Some((resolve_path(&self.package.path, module), "module")); } } if self.fields.contains(Fields::MAIN) { self.fields.remove(Fields::MAIN); - if let Some(main) = &self.package.main { + if let Some(main) = self.package.main { return Some((resolve_path(&self.package.path, main), "main")); } } if self.fields.contains(Fields::TSCONFIG) { self.fields.remove(Fields::TSCONFIG); - if let Some(tsconfig) = &self.package.tsconfig { + if let Some(tsconfig) = self.package.tsconfig { return Some((resolve_path(&self.package.path, tsconfig), "tsconfig")); } } @@ -753,8 +812,8 @@ mod tests { fn exports_string() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), - exports: ExportsField::String(String::from("./exports.js")), + name: "foobar", + exports: ExportsField::String("./exports.js"), ..PackageJson::default() }; @@ -772,9 +831,9 @@ mod tests { fn exports_dot() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - ".".into() => ExportsField::String(String::from("./exports.js")) + ".".into() => ExportsField::String("./exports.js") }), ..PackageJson::default() }; @@ -796,11 +855,11 @@ mod tests { fn exports_dot_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { ".".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String(String::from("./import.js")), - "require".into() => ExportsField::String(String::from("./require.js")) + "import".into() => ExportsField::String("./import.js"), + "require".into() => ExportsField::String("./require.js") }) }), ..PackageJson::default() @@ -836,12 +895,12 @@ mod tests { fn exports_map_string() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "./foo".into() => ExportsField::String(String::from("./exports.js")), - "./.invisible".into() => ExportsField::String(String::from("./.invisible.js")), - "./".into() => ExportsField::String(String::from("./")), - "./*".into() => ExportsField::String(String::from("./*.js")) + "./foo".into() => ExportsField::String("./exports.js"), + "./.invisible".into() => ExportsField::String("./.invisible.js"), + "./".into() => ExportsField::String("./"), + "./*".into() => ExportsField::String("./*.js") }), ..PackageJson::default() }; @@ -870,11 +929,11 @@ mod tests { fn exports_map_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { "./foo".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String(String::from("./import.js")), - "require".into() => ExportsField::String(String::from("./require.js")) + "import".into() => ExportsField::String("./import.js"), + "require".into() => ExportsField::String("./require.js") }) }), ..PackageJson::default() @@ -910,13 +969,13 @@ mod tests { fn nested_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { "node".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String(String::from("./import.js")), - "require".into() => ExportsField::String(String::from("./require.js")) + "import".into() => ExportsField::String("./import.js"), + "require".into() => ExportsField::String("./require.js") }), - "default".into() => ExportsField::String(String::from("./default.js")) + "default".into() => ExportsField::String("./default.js") }), ..PackageJson::default() }; @@ -957,10 +1016,10 @@ mod tests { fn custom_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "custom".into() => ExportsField::String(String::from("./custom.js")), - "default".into() => ExportsField::String(String::from("./default.js")) + "custom".into() => ExportsField::String("./custom.js"), + "default".into() => ExportsField::String("./default.js") }), ..PackageJson::default() }; @@ -982,16 +1041,16 @@ mod tests { fn subpath_nested_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { "./lite".into() => ExportsField::Map(indexmap! { "node".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String(String::from("./node_import.js")), - "require".into() => ExportsField::String(String::from("./node_require.js")) + "import".into() => ExportsField::String("./node_import.js"), + "require".into() => ExportsField::String("./node_require.js") }), "browser".into() => ExportsField::Map(indexmap! { - "import".into() => ExportsField::String(String::from("./browser_import.js")), - "require".into() => ExportsField::String(String::from("./browser_require.js")) + "import".into() => ExportsField::String("./browser_import.js"), + "require".into() => ExportsField::String("./browser_require.js") }), }) }), @@ -1048,12 +1107,12 @@ mod tests { fn subpath_star() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "./*".into() => ExportsField::String(String::from("./cheese/*.mjs")), - "./pizza/*".into() => ExportsField::String(String::from("./pizza/*.mjs")), - "./burritos/*".into() => ExportsField::String(String::from("./burritos/*/*.mjs")), - "./literal".into() => ExportsField::String(String::from("./literal/*.js")), + "./*".into() => ExportsField::String("./cheese/*.mjs"), + "./pizza/*".into() => ExportsField::String("./pizza/*.mjs"), + "./burritos/*".into() => ExportsField::String("./burritos/*/*.mjs"), + "./literal".into() => ExportsField::String("./literal/*.js"), }), ..PackageJson::default() }; @@ -1097,9 +1156,9 @@ mod tests { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "./*".into() => ExportsField::String(String::from("./*.js")), + "./*".into() => ExportsField::String("./*.js"), "./*.js".into() => ExportsField::None, "./internal/*".into() => ExportsField::None, }), @@ -1125,9 +1184,9 @@ mod tests { fn exports_null() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "./features/*.js".into() => ExportsField::String(String::from("./src/features/*.js")), + "./features/*.js".into() => ExportsField::String("./src/features/*.js"), "./features/private-internal/*".into() => ExportsField::None, }), ..PackageJson::default() @@ -1159,18 +1218,18 @@ mod tests { fn exports_array() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { "./utils/*".into() => ExportsField::Map(indexmap! { "browser".into() => ExportsField::Map(indexmap! { - "worklet".into() => ExportsField::Array(vec![ExportsField::String(String::from("./*")), ExportsField::String(String::from("./node/*"))]), + "worklet".into() => ExportsField::Array(vec![ExportsField::String("./*"), ExportsField::String("./node/*")]), "default".into() => ExportsField::Map(indexmap! { - "node".into() => ExportsField::String(String::from("./node/*")) + "node".into() => ExportsField::String("./node/*") }) }) }), - "./test/*".into() => ExportsField::Array(vec![ExportsField::String(String::from("lodash/*")), ExportsField::String(String::from("./bar/*"))]), - "./file".into() => ExportsField::Array(vec![ExportsField::String(String::from("http://a.com")), ExportsField::String(String::from("./file.js"))]) + "./test/*".into() => ExportsField::Array(vec![ExportsField::String("lodash/*"), ExportsField::String("./bar/*")]), + "./file".into() => ExportsField::Array(vec![ExportsField::String("http://a.com"), ExportsField::String("./file.js")]) }), ..PackageJson::default() }; @@ -1218,12 +1277,12 @@ mod tests { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Array(vec![ ExportsField::Map(indexmap! { - "node".into() => ExportsField::String(String::from("./a.js")) + "node".into() => ExportsField::String("./a.js") }), - ExportsField::String(String::from("./b.js")), + ExportsField::String("./b.js"), ]), ..PackageJson::default() }; @@ -1246,16 +1305,16 @@ mod tests { fn exports_invalid() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - "./invalid".into() => ExportsField::String(String::from("../invalid")), - "./absolute".into() => ExportsField::String(String::from("/absolute")), - "./package".into() => ExportsField::String(String::from("package")), - "./utils/index".into() => ExportsField::String(String::from("./src/../index.js")), - "./dist/*".into() => ExportsField::String(String::from("./src/../../*")), - "./modules/*".into() => ExportsField::String(String::from("./node_modules/*")), - "./modules2/*".into() => ExportsField::String(String::from("./NODE_MODULES/*")), - "./*/*".into() => ExportsField::String(String::from("./file.js")) + "./invalid".into() => ExportsField::String("../invalid"), + "./absolute".into() => ExportsField::String("/absolute"), + "./package".into() => ExportsField::String("package"), + "./utils/index".into() => ExportsField::String("./src/../index.js"), + "./dist/*".into() => ExportsField::String("./src/../../*"), + "./modules/*".into() => ExportsField::String("./node_modules/*"), + "./modules2/*".into() => ExportsField::String("./NODE_MODULES/*"), + "./*/*".into() => ExportsField::String("./file.js") }), ..PackageJson::default() }; @@ -1295,10 +1354,10 @@ mod tests { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", exports: ExportsField::Map(indexmap! { - ".".into() => ExportsField::String(String::from("./foo.js")), - "node".into() => ExportsField::String(String::from("./bar.js")), + ".".into() => ExportsField::String("./foo.js"), + "node".into() => ExportsField::String("./bar.js"), }), ..PackageJson::default() }; @@ -1317,11 +1376,11 @@ mod tests { fn imports() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", imports: indexmap! { - "#foo".into() => ExportsField::String(String::from("./foo.mjs")), - "#internal/*".into() => ExportsField::String(String::from("./src/internal/*.mjs")), - "#bar".into() => ExportsField::String(String::from("bar")), + "#foo".into() => ExportsField::String("./foo.mjs"), + "#internal/*".into() => ExportsField::String("./src/internal/*.mjs"), + "#bar".into() => ExportsField::String("bar"), }, ..PackageJson::default() }; @@ -1350,11 +1409,11 @@ mod tests { fn import_conditions() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", imports: indexmap! { "#entry/*".into() => ExportsField::Map(indexmap! { - "node".into() => ExportsField::String(String::from("./node/*.js")), - "browser".into() => ExportsField::String(String::from("./browser/*.js")) + "node".into() => ExportsField::String("./node/*.js"), + "browser".into() => ExportsField::String("./browser/*.js") }) }, ..PackageJson::default() @@ -1387,7 +1446,7 @@ mod tests { fn aliases() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", alias: indexmap! { "./foo.js".into() => AliasValue::Specifier("./foo-alias.js".into()), "bar".into() => AliasValue::Specifier("./bar-alias.js".into()), @@ -1498,7 +1557,7 @@ mod tests { fn side_effects_none() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", ..PackageJson::default() }; @@ -1511,7 +1570,7 @@ mod tests { fn side_effects_bool() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), + name: "foobar", side_effects: SideEffects::Boolean(false), ..PackageJson::default() }; @@ -1534,8 +1593,8 @@ mod tests { fn side_effects_glob() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), - side_effects: SideEffects::String(String::from("*.css")), + name: "foobar", + side_effects: SideEffects::String("*.css"), ..PackageJson::default() }; @@ -1547,7 +1606,7 @@ mod tests { assert!(pkg.has_side_effects(Path::new("/index.js"))); let pkg = PackageJson { - side_effects: SideEffects::String(String::from("bar/*.css")), + side_effects: SideEffects::String("bar/*.css"), ..pkg }; @@ -1559,7 +1618,7 @@ mod tests { assert!(pkg.has_side_effects(Path::new("/index.js"))); let pkg = PackageJson { - side_effects: SideEffects::String(String::from("./bar/*.css")), + side_effects: SideEffects::String("./bar/*.css"), ..pkg }; @@ -1575,8 +1634,8 @@ mod tests { fn side_effects_array() { let pkg = PackageJson { path: "/foo/package.json".into(), - name: String::from("foobar"), - side_effects: SideEffects::Array(vec![String::from("*.css"), String::from("*.html")]), + name: "foobar", + side_effects: SideEffects::Array(vec!["*.css", "*.html"]), ..PackageJson::default() }; diff --git a/packages/utils/node-resolver-rs/src/path.rs b/packages/utils/node-resolver-rs/src/path.rs index 4e27412c3cf..c5fdd7e686e 100644 --- a/packages/utils/node-resolver-rs/src/path.rs +++ b/packages/utils/node-resolver-rs/src/path.rs @@ -1,6 +1,8 @@ -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; +#[cfg(not(target_arch = "wasm32"))] +use crate::fs::FileSystemRealPathCache; +#[cfg(not(target_arch = "wasm32"))] +use std::collections::VecDeque; +use std::path::{Component, Path, PathBuf}; pub fn normalize_path(path: &Path) -> PathBuf { // Normalize path components to resolve ".." and "." segments. @@ -56,3 +58,152 @@ pub fn resolve_path, B: AsRef>(base: A, subpath: B) -> Path ret } + +#[cfg(not(target_arch = "wasm32"))] +/// A reimplementation of std::fs::canonicalize with intermediary caching. +pub fn canonicalize(path: &Path, cache: &FileSystemRealPathCache) -> std::io::Result { + let mut ret = PathBuf::new(); + let mut seen_links = 0; + let mut queue = VecDeque::new(); + + queue.push_back(path); + + while let Some(cur_path) = queue.pop_front() { + let mut components = cur_path.components(); + for component in &mut components { + match component { + Component::Prefix(c) => ret.push(c.as_os_str()), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + + // First, check the cache for the path up to this point. + let link: &Path = if let Some(cached) = cache.get(&ret) { + if let Some(link) = &*cached { + // SAFETY: Keys are never removed from the cache or mutated + // and PathBuf has a stable address for path data even when moved. + unsafe { &*(link.as_path() as *const _) } + } else { + continue; + } + } else { + let stat = std::fs::symlink_metadata(&ret)?; + if !stat.is_symlink() { + cache.insert(ret.clone(), None); + continue; + } + + let link = std::fs::read_link(&ret)?; + let ptr = unsafe { &*(link.as_path() as *const _) }; + cache.insert(ret.clone(), Some(link)); + ptr + }; + + seen_links += 1; + if seen_links > 32 { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Too many symlinks", + )); + } + + // If the link is absolute, replace the result path + // with it, otherwise remove the last segment and + // resolve the link components next. + if link.is_absolute() { + ret = PathBuf::new(); + } else { + ret.pop(); + } + + let remaining = components.as_path(); + if !remaining.as_os_str().is_empty() { + queue.push_front(remaining); + } + queue.push_front(link); + break; + } + } + } + } + + Ok(ret) +} + +#[cfg(test)] +mod test { + use super::*; + use assert_fs::prelude::*; + use dashmap::DashMap; + + #[test] + fn test_canonicalize() -> Result<(), Box> { + #[cfg(windows)] + if !is_elevated::is_elevated() { + println!("skipping symlink tests due to missing permissions"); + return Ok(()); + } + + let dir = assert_fs::TempDir::new()?; + dir.child("foo/bar.js").write_str("")?; + dir.child("root.js").write_str("")?; + + dir + .child("symlink") + .symlink_to_file(Path::new("foo").join("bar.js"))?; + dir + .child("foo/symlink") + .symlink_to_file(Path::new("..").join("root.js"))?; + dir + .child("absolute") + .symlink_to_file(dir.child("root.js").path())?; + dir + .child("recursive") + .symlink_to_file(Path::new("foo").join("symlink"))?; + dir.child("cycle").symlink_to_file("cycle1")?; + dir.child("cycle1").symlink_to_file("cycle")?; + dir.child("a/b/c").create_dir_all()?; + dir.child("a/b/e").symlink_to_file("..")?; + dir.child("a/d").symlink_to_file("..")?; + dir.child("a/b/c/x.txt").write_str("")?; + dir + .child("a/link") + .symlink_to_file(dir.child("a/b").path())?; + + let cache = DashMap::default(); + + assert_eq!( + canonicalize(dir.child("symlink").path(), &cache)?, + canonicalize(dir.child("foo/bar.js").path(), &cache)? + ); + assert_eq!( + canonicalize(dir.child("foo/symlink").path(), &cache)?, + canonicalize(dir.child("root.js").path(), &cache)? + ); + assert_eq!( + canonicalize(dir.child("absolute").path(), &cache)?, + canonicalize(dir.child("root.js").path(), &cache)? + ); + assert_eq!( + canonicalize(dir.child("recursive").path(), &cache)?, + canonicalize(dir.child("root.js").path(), &cache)? + ); + assert!(canonicalize(dir.child("cycle").path(), &cache).is_err()); + assert_eq!( + canonicalize(dir.child("a/b/e/d/a/b/e/d/a").path(), &cache)?, + canonicalize(dir.child("a").path(), &cache)? + ); + assert_eq!( + canonicalize(dir.child("a/link/c/x.txt").path(), &cache)?, + canonicalize(dir.child("a/b/c/x.txt").path(), &cache)? + ); + + Ok(()) + } +} diff --git a/packages/utils/node-resolver-rs/src/specifier.rs b/packages/utils/node-resolver-rs/src/specifier.rs index 0e1510dd4b3..f42573ddd8e 100644 --- a/packages/utils/node-resolver-rs/src/specifier.rs +++ b/packages/utils/node-resolver-rs/src/specifier.rs @@ -1,12 +1,11 @@ -use std::borrow::Cow; -use std::path::is_separator; -use std::path::PathBuf; +use std::{ + borrow::Cow, + path::{is_separator, Path, PathBuf}, +}; use percent_encoding::percent_decode_str; -use crate::builtins::BUILTINS; -use crate::url_to_path::url_to_path; -use crate::Flags; +use crate::{builtins::BUILTINS, url_to_path::url_to_path, Flags}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SpecifierType { @@ -40,22 +39,22 @@ where } #[derive(PartialEq, Eq, Hash, Clone, Debug)] -pub enum Specifier { - Relative(PathBuf), - Absolute(PathBuf), - Tilde(PathBuf), - Hash(String), - Package(String, String), - Builtin(String), - Url(String), +pub enum Specifier<'a> { + Relative(Cow<'a, Path>), + Absolute(Cow<'a, Path>), + Tilde(Cow<'a, Path>), + Hash(Cow<'a, str>), + Package(Cow<'a, str>, Cow<'a, str>), + Builtin(Cow<'a, str>), + Url(&'a str), } -impl Specifier { +impl<'a> Specifier<'a> { pub fn parse( - specifier: &str, + specifier: &'a str, specifier_type: SpecifierType, flags: Flags, - ) -> Result<(Specifier, Option<&str>), SpecifierError> { + ) -> Result<(Specifier<'a>, Option<&'a str>), SpecifierError> { if specifier.is_empty() { return Err(SpecifierError::EmptySpecifier); } @@ -81,13 +80,13 @@ impl Specifier { b'/' => { if specifier.starts_with("//") && specifier_type == SpecifierType::Url { // A protocol-relative URL, e.g `url('//example.com/foo.png')`. - (Specifier::Url(String::from(specifier)), None) + (Specifier::Url(specifier), None) } else { let (path, query) = decode_path(specifier, specifier_type); (Specifier::Absolute(path), query) } } - b'#' => (Specifier::Hash(String::from(&specifier[1..])), None), + b'#' => (Specifier::Hash(Cow::Borrowed(&specifier[1..])), None), _ => { // Bare specifier. match specifier_type { @@ -99,7 +98,7 @@ impl Specifier { match scheme.as_ref() { "npm" if flags.contains(Flags::NPM_SCHEME) => { if BUILTINS.contains(&path) { - return Ok((Specifier::Builtin(String::from(path)), None)); + return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); } ( @@ -110,10 +109,13 @@ impl Specifier { "node" => { // Node does not URL decode or support query params here. // See https://github.com/nodejs/node/issues/39710. - (Specifier::Builtin(String::from(path)), None) + (Specifier::Builtin(Cow::Borrowed(path)), None) } - "file" => (Specifier::Absolute(url_to_path(specifier)?), query), - _ => (Specifier::Url(String::from(specifier)), None), + "file" => ( + Specifier::Absolute(Cow::Owned(url_to_path(specifier)?)), + query, + ), + _ => (Specifier::Url(specifier), None), } } else { // If not, then parse as an npm package if this is an ESM specifier, @@ -121,7 +123,7 @@ impl Specifier { let (path, rest) = parse_path(specifier); if specifier_type == SpecifierType::Esm { if BUILTINS.contains(&path) { - return Ok((Specifier::Builtin(String::from(path)), None)); + return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); } let (query, _) = parse_query(rest); @@ -137,17 +139,17 @@ impl Specifier { } SpecifierType::Cjs => { if let Some(node_prefixed) = specifier.strip_prefix("node:") { - return Ok((Specifier::Builtin(String::from(node_prefixed)), None)); + return Ok((Specifier::Builtin(Cow::Borrowed(node_prefixed)), None)); } if BUILTINS.contains(&specifier) { - (Specifier::Builtin(String::from(specifier)), None) + (Specifier::Builtin(Cow::Borrowed(specifier)), None) } else { #[cfg(windows)] if !flags.contains(Flags::ABSOLUTE_SPECIFIERS) { - let path = std::path::PathBuf::from(specifier); + let path = Path::new(specifier); if path.is_absolute() { - return Ok((Specifier::Absolute(path), None)); + return Ok((Specifier::Absolute(Cow::Borrowed(path)), None)); } } @@ -159,12 +161,12 @@ impl Specifier { }) } - pub fn to_string<'a>(&'a self) -> Cow<'a, str> { + pub fn to_string(&'a self) -> Cow<'a, str> { match self { Specifier::Relative(path) | Specifier::Absolute(path) | Specifier::Tilde(path) => { path.as_os_str().to_string_lossy() } - Specifier::Hash(path) => Cow::Borrowed(path), + Specifier::Hash(path) => path.clone(), Specifier::Package(module, subpath) => { if subpath.is_empty() { Cow::Borrowed(module) @@ -244,11 +246,17 @@ fn parse_package(specifier: Cow<'_, str>) -> Result { match specifier { Cow::Borrowed(specifier) => { let (module, subpath) = parse_package_specifier(specifier)?; - Ok(Specifier::Package(module.to_string(), subpath.to_string())) + Ok(Specifier::Package( + Cow::Borrowed(module), + Cow::Borrowed(subpath), + )) } Cow::Owned(specifier) => { let (module, subpath) = parse_package_specifier(&specifier)?; - Ok(Specifier::Package(module.to_owned(), subpath.to_owned())) + Ok(Specifier::Package( + Cow::Owned(module.to_owned()), + Cow::Owned(subpath.to_owned()), + )) } } } @@ -272,36 +280,42 @@ pub fn parse_package_specifier(specifier: &str) -> Result<(&str, &str), Specifie } } -pub fn decode_path(specifier: &str, specifier_type: SpecifierType) -> (PathBuf, Option<&str>) { +pub fn decode_path( + specifier: &str, + specifier_type: SpecifierType, +) -> (Cow<'_, Path>, Option<&str>) { match specifier_type { SpecifierType::Url | SpecifierType::Esm => { let (path, rest) = parse_path(specifier); let (query, _) = parse_query(rest); - let path = PathBuf::from(percent_decode_str(path).decode_utf8_lossy().to_string()); + let path = match percent_decode_str(path).decode_utf8_lossy() { + Cow::Borrowed(v) => Cow::Borrowed(Path::new(v)), + Cow::Owned(v) => Cow::Owned(PathBuf::from(v)), + }; (path, query) } - SpecifierType::Cjs => (PathBuf::from(specifier), None), + SpecifierType::Cjs => (Cow::Borrowed(Path::new(specifier)), None), } } -impl From<&str> for Specifier { - fn from(specifier: &str) -> Self { +impl<'a> From<&'a str> for Specifier<'a> { + fn from(specifier: &'a str) -> Self { Specifier::parse(specifier, SpecifierType::Cjs, Flags::empty()) .unwrap() .0 } } -impl<'de> serde::Deserialize<'de> for Specifier { +impl<'a, 'de: 'a> serde::Deserialize<'de> for Specifier<'a> { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; - let s: String = Deserialize::deserialize(deserializer)?; + let s: &'de str = Deserialize::deserialize(deserializer)?; // Specifiers are only deserialized as part of the "alias" and "browser" fields, // so we assume CJS specifiers in Parcel mode. - Specifier::parse(&s, SpecifierType::Cjs, Flags::empty()) + Specifier::parse(s, SpecifierType::Cjs, Flags::empty()) .map(|s| s.0) .map_err(|_| serde::de::Error::custom("Invalid specifier")) } diff --git a/packages/utils/node-resolver-rs/src/tsconfig.rs b/packages/utils/node-resolver-rs/src/tsconfig.rs index e1a2e927cbf..5d5d76deb74 100644 --- a/packages/utils/node-resolver-rs/src/tsconfig.rs +++ b/packages/utils/node-resolver-rs/src/tsconfig.rs @@ -1,27 +1,29 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; -use crate::path::resolve_path; -use crate::specifier::Specifier; +use indexmap::IndexMap; use itertools::Either; -use json_comments::StripComments; +use json_comments::strip_comments_in_place; -#[derive(serde::Deserialize, Clone, Debug, Default)] +use crate::{path::resolve_path, specifier::Specifier}; + +#[derive(serde::Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] -pub struct TsConfig { +pub struct TsConfig<'a> { #[serde(skip)] pub path: PathBuf, - base_url: Option, - paths: Option>>, + base_url: Option>, + #[serde(borrow)] + paths: Option, Vec<&'a str>>>, #[serde(skip)] paths_base: PathBuf, - pub module_suffixes: Option>, + pub module_suffixes: Option>, // rootDirs?? } -fn deserialize_extends<'a, 'de: 'a, D>(deserializer: D) -> Result, D::Error> +fn deserialize_extends<'a, 'de: 'a, D>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, { @@ -29,9 +31,10 @@ where #[derive(serde::Deserialize)] #[serde(untagged)] - enum StringOrArray { - String(Specifier), - Array(Vec), + enum StringOrArray<'a> { + #[serde(borrow)] + String(Specifier<'a>), + Array(Vec>), } Ok(match StringOrArray::deserialize(deserializer)? { @@ -42,17 +45,17 @@ where #[derive(serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct TsConfigWrapper { - #[serde(default, deserialize_with = "deserialize_extends")] - pub extends: Vec, +pub struct TsConfigWrapper<'a> { + #[serde(borrow, default, deserialize_with = "deserialize_extends")] + pub extends: Vec>, #[serde(default)] - pub compiler_options: TsConfig, + pub compiler_options: TsConfig<'a>, } -impl TsConfig { - pub fn parse(path: PathBuf, data: &str) -> serde_json5::Result { - let mut stripped = StripComments::new(data.as_bytes()); - let mut wrapper: TsConfigWrapper = serde_json5::from_reader(&mut stripped)?; +impl<'a> TsConfig<'a> { + pub fn parse(path: PathBuf, data: &'a mut str) -> serde_json::Result> { + let _ = strip_comments_in_place(data, Default::default(), true); + let mut wrapper: TsConfigWrapper = serde_json::from_str(data)?; wrapper.compiler_options.path = path; wrapper.compiler_options.validate(); Ok(wrapper) @@ -60,19 +63,19 @@ impl TsConfig { fn validate(&mut self) { if let Some(base_url) = &mut self.base_url { - *base_url = resolve_path(&self.path, &base_url); + *base_url = Cow::Owned(resolve_path(&self.path, &base_url)); } if self.paths.is_some() { self.paths_base = if let Some(base_url) = &self.base_url { - base_url.to_owned() + base_url.as_ref().to_owned() } else { self.path.parent().unwrap().to_owned() }; } } - pub fn extend(&mut self, extended: &TsConfig) { + pub fn extend(&mut self, extended: &TsConfig<'a>) { if self.base_url.is_none() { self.base_url = extended.base_url.clone(); } @@ -87,7 +90,7 @@ impl TsConfig { } } - pub fn paths<'a>(&'a self, specifier: &'a Specifier) -> impl Iterator + 'a { + pub fn paths(&'a self, specifier: &'a Specifier) -> impl Iterator + 'a { if !matches!(specifier, Specifier::Package(..) | Specifier::Builtin(..)) { return Either::Right(Either::Right(std::iter::empty())); } @@ -151,7 +154,7 @@ impl TsConfig { fn join_paths<'a>( base_url: &'a Path, - paths: &'a [String], + paths: &'a [&'a str], replacement: Option<(Cow<'a, str>, usize, usize)>, ) -> impl Iterator + 'a { paths @@ -174,8 +177,8 @@ fn base_url_iter<'a>( std::iter::once_with(move || { let mut path = base_url.to_owned(); if let Specifier::Package(module, subpath) = specifier { - path.push(module.as_str()); - path.push(subpath.as_str()); + path.push(module.as_ref()); + path.push(subpath.as_ref()); } path }) @@ -183,26 +186,22 @@ fn base_url_iter<'a>( #[cfg(test)] mod tests { + use indexmap::indexmap; + use super::*; #[test] fn test_paths() { let mut tsconfig = TsConfig { path: "/foo/tsconfig.json".into(), - paths: Some(HashMap::from([ - ( - "jquery".into(), - vec![String::from("node_modules/jquery/dist/jquery")], - ), - ("*".into(), vec![String::from("generated/*")]), - ("bar/*".into(), vec![String::from("test/*")]), - ( - "bar/baz/*".into(), - vec![String::from("baz/*"), String::from("yo/*")], - ), - ("@/components/*".into(), vec![String::from("components/*")]), - ("url".into(), vec![String::from("node_modules/my-url")]), - ])), + paths: Some(indexmap! { + "jquery".into() => vec!["node_modules/jquery/dist/jquery"], + "*".into() => vec!["generated/*"], + "bar/*".into() => vec!["test/*"], + "bar/baz/*".into() => vec!["baz/*", "yo/*"], + "@/components/*".into() => vec!["components/*"], + "url".into() => vec!["node_modules/my-url"], + }), ..Default::default() }; tsconfig.validate(); @@ -255,15 +254,12 @@ mod tests { let mut tsconfig = TsConfig { path: "/foo/tsconfig.json".into(), base_url: Some(Path::new("src").into()), - paths: Some(HashMap::from([ - ("*".into(), vec![String::from("generated/*")]), - ("bar/*".into(), vec![String::from("test/*")]), - ( - "bar/baz/*".into(), - vec![String::from("baz/*"), String::from("yo/*")], - ), - ("@/components/*".into(), vec![String::from("components/*")]), - ])), + paths: Some(indexmap! { + "*".into() => vec!["generated/*"], + "bar/*".into() => vec!["test/*"], + "bar/baz/*".into() => vec!["baz/*", "yo/*"], + "@/components/*".into() => vec!["components/*"], + }), ..Default::default() }; tsconfig.validate(); @@ -308,30 +304,4 @@ mod tests { ); assert_eq!(test("./jquery"), Vec::::new()); } - - #[test] - fn test_deserialize() { - let config = r#" -{ - "compilerOptions": { - "paths": { - /* some comment */ - "foo": ["bar.js"] - } - } - // another comment -} - "#; - let result: TsConfigWrapper = TsConfig::parse(PathBuf::from("stub.json"), config).unwrap(); - assert_eq!(result.extends, vec![]); - assert!(result.compiler_options.paths.is_some()); - assert_eq!( - result - .compiler_options - .paths - .unwrap() - .get(&Specifier::from("foo")), - Some(&vec![String::from("bar.js")]) - ); - } } diff --git a/packages/utils/node-resolver-rs/src/url_to_path.rs b/packages/utils/node-resolver-rs/src/url_to_path.rs index f78a842c48c..2eeffebcdb7 100644 --- a/packages/utils/node-resolver-rs/src/url_to_path.rs +++ b/packages/utils/node-resolver-rs/src/url_to_path.rs @@ -29,7 +29,7 @@ pub fn url_to_path(input: &str) -> Result { #[cfg(any(target_arch = "wasm32", test))] #[inline] fn os_str_from_bytes(slice: &[u8]) -> &OsStr { - OsStr::new(std::str::from_utf8(slice).expect("Non UTF-8 string")) + unsafe { std::mem::transmute(slice) } } #[cfg(test)] diff --git a/yarn.lock b/yarn.lock index 278b065faf1..9cf680a2fa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10116,6 +10116,11 @@ napi-wasm@^1.0.1: resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.0.1.tgz#b186b9f9f1a54c321c4a1ea852a31a9691792a24" integrity sha512-70Ks5fMTw5/5iZJg8dtyivwPP9tgyeLRHGa2j4GlFOjz4lWbbTWrDAvSeJlQg+o++cWpX2bSPGI1Hu4W2kbOoA== +napi-wasm@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.2.tgz#d05a85222499c49e56d953e40b868110ebc294b1" + integrity sha512-yaA+PVpjOX7xdMmIZwx2IfzKmf1+3fBfshLTB3bvwZAUY2/3UBqOnzMRxwAP0hhxkpRRNtvd/dg4mDSJ/2CE6Q== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -13461,7 +13466,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13487,15 +13492,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -13574,7 +13570,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13595,13 +13591,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -15100,7 +15089,7 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15126,15 +15115,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"