From 3caf5c04c9da5fe32f67f07cfb34451b9d8071c6 Mon Sep 17 00:00:00 2001
From: Jeremy Maitin-Shepard
Date: Sat, 20 Jan 2024 22:37:17 -0800
Subject: [PATCH] chore: use vite/vitest in place of esbuild/karma+jasmine
Neuroglancer can now be used as a regular NPM dependency with vite, parcel, or
webpack as the bundler (but unfortunately not esbuild due to
https://github.com/evanw/esbuild/issues/795), and can be installed as a
dependency via a git URL.
Node.js import/export conditions are now used to enable/disable layer types and
datasources, which can be configured by dependent projects rather than when
Neuroglancer itself is built/published.
The Python tests accept a `--build-client` option to run the dev server
automatically rather than requiring a pre-built Python Neuroglancer client.
---
.eslintignore | 12 +
.eslintrc | 19 -
.eslintrc.yml | 67 +
.github/workflows/build.yml | 70 +-
.gitignore | 4 +-
.prettierignore | 3 +
.prettierrc.yml | 4 +
CONTRIBUTING.md | 2 +-
MANIFEST.in | 8 +
biome.json | 68 -
build_tools/build-package.ts | 170 +
build_tools/cli.ts | 266 +
build_tools/postpack.ts | 11 +
build_tools/update-conditions.ts | 141 +
build_tools/vite/vite-plugin-binary.ts | 35 +
.../vite/vite-plugin-dev-server-path-map.ts | 18 +
config/bundle-config.js | 257 -
config/config.js | 10 -
config/esbuild-cli.js | 234 -
config/esbuild-dev-server.js | 81 -
config/esbuild.js | 327 -
config/esbuild_svg_inline_loader.js | 35 -
config/{generate_code.js => generate-code.ts} | 0
config/karma-entry-points.js | 75 -
config/karma.benchmark.js | 50 -
config/karma.conf.js | 52 -
config/resolve_real.js | 31 -
config/static-site-live-server.js | 359 -
examples/README.md | 47 +
examples/parcel/README.md | 90 +
.../parcel/parcel-project-built/.gitignore | 3 +
.../parcel/parcel-project-built/.parcelrc | 9 +
.../parcel/parcel-project-built/index.html | 8 +
.../parcel-project-built/package-lock.json | 4307 +++++
.../parcel/parcel-project-built/package.json | 29 +
.../parcel/parcel-project-built/src/index.js | 3 +
.../parcel-project-built/svgo.config.json | 13 +
.../parcel/parcel-project-source/.gitignore | 3 +
.../parcel/parcel-project-source/.parcelrc | 9 +
.../parcel/parcel-project-source/index.html | 8 +
.../parcel-project-source/package-lock.json | 4383 +++++
.../parcel/parcel-project-source/package.json | 29 +
.../parcel/parcel-project-source/src/index.js | 3 +
.../parcel-project-source/svgo.config.json | 13 +
.../parcel-project-source/tsconfig.json | 5 +
examples/vite/vite-project-built/.gitignore | 2 +
examples/vite/vite-project-built/index.html | 11 +
.../vite/vite-project-built/package-lock.json | 994 +
examples/vite/vite-project-built/package.json | 18 +
.../vite/vite-project-built/vite.config.ts | 30 +
examples/vite/vite-project-source/.gitignore | 2 +
examples/vite/vite-project-source/index.html | 11 +
.../vite-project-source/package-lock.json | 1070 ++
.../vite/vite-project-source/package.json | 18 +
.../vite/vite-project-source/vite.config.ts | 30 +
.../webpack/webpack-project-built/.gitignore | 2 +
.../webpack/webpack-project-built/README.md | 1 +
.../webpack-project-built/package-lock.json | 4200 +++++
.../webpack-project-built/package.json | 29 +
.../webpack-project-built/src/index.js | 3 +
.../webpack-project-built/webpack.config.js | 49 +
.../webpack/webpack-project-source/.gitignore | 2 +
.../webpack-project-source/package-lock.json | 4764 +++++
.../webpack-project-source/package.json | 30 +
.../webpack-project-source/src/index.js | 3 +
.../webpack-project-source/webpack.config.js | 59 +
index.html | 7 +
package-lock.json | 15121 ++++++++++------
package.json | 425 +-
pyproject.toml | 2 +
python/build_tools/cibuildwheel.sh | 2 +-
python/neuroglancer/__init__.py | 1 +
python/neuroglancer/cli.py | 9 +-
python/neuroglancer/server.py | 80 +-
python/neuroglancer/static/.gitignore | 6 +-
python/neuroglancer/static/__init__.py | 16 +-
python/neuroglancer/webdriver.py | 65 +-
python/requirements-mypy.txt | 3 +
python/requirements-test-browser.txt | 2 +
python/requirements-test.txt | 8 +
python/tests/client_test.py | 321 +
python/tests/conftest.py | 31 +-
...er_controls.py => shader_controls_test.py} | 0
setup.py | 100 +-
src/annotation/annotation_layer_state.ts | 44 +-
src/annotation/backend.ts | 62 +-
src/annotation/base.ts | 15 +-
src/annotation/bounding_box.ts | 38 +-
src/annotation/ellipsoid.ts | 29 +-
...pec.ts => frontend_source.browser_test.ts} | 15 +-
src/annotation/frontend_source.ts | 70 +-
src/annotation/index.ts | 25 +-
src/annotation/line.ts | 23 +-
src/annotation/point.ts | 21 +-
src/annotation/renderlayer.ts | 145 +-
src/annotation/selection.ts | 2 +-
src/annotation/type_handler.ts | 47 +-
src/async_computation.bundle.js | 6 +
src/async_computation/decode_blosc.ts | 8 +-
src/async_computation/decode_blosc_request.ts | 2 +-
src/async_computation/decode_compresso.ts | 6 +-
.../decode_compresso_request.ts | 2 +-
src/async_computation/decode_gzip.ts | 4 +-
src/async_computation/decode_gzip_request.ts | 2 +-
src/async_computation/decode_jpeg.ts | 8 +-
src/async_computation/decode_jpeg_request.ts | 4 +-
src/async_computation/decode_png.ts | 6 +-
src/async_computation/decode_png_request.ts | 2 +-
src/async_computation/decode_zstd.ts | 8 +-
src/async_computation/decode_zstd_request.ts | 2 +-
.../encode_compressed_segmentation.ts | 10 +-
.../encode_compressed_segmentation_request.ts | 2 +-
src/async_computation/handler.ts | 2 +-
src/async_computation/obj_mesh.ts | 14 +-
src/async_computation/obj_mesh_request.ts | 4 +-
src/async_computation/request.ts | 34 +-
src/async_computation/vtk_mesh.ts | 8 +-
src/async_computation/vtk_mesh_request.ts | 4 +-
src/axes_lines.ts | 14 +-
src/chunk_manager/backend.ts | 48 +-
src/chunk_manager/base.ts | 4 +
src/chunk_manager/frontend.ts | 44 +-
src/chunk_manager/generic_file_source.ts | 23 +-
src/chunk_worker.bundle.js | 11 +
src/coordinate_transform.spec.ts | 16 +-
src/coordinate_transform.ts | 25 +-
.../chunk_source_frontend.ts | 12 +-
src/credentials_provider/default_manager.ts | 2 +-
src/credentials_provider/http_request.ts | 15 +-
src/credentials_provider/index.ts | 11 +-
src/credentials_provider/oauth2.ts | 10 +-
src/credentials_provider/shared.ts | 19 +-
.../shared_counterpart.ts | 15 +-
src/data_panel_layout.ts | 63 +-
src/datasource/boss/api.ts | 10 +-
src/datasource/boss/async_computation.ts | 2 +
src/datasource/boss/backend.ts | 37 +-
src/datasource/boss/base.ts | 2 +-
src/datasource/boss/credentials_provider.ts | 18 +-
src/datasource/boss/frontend.ts | 61 +-
.../boss/register_credentials_provider.ts | 6 +-
src/datasource/boss/register_default.ts | 4 +-
src/datasource/brainmaps/api.ts | 13 +-
src/datasource/brainmaps/async_computation.ts | 1 +
src/datasource/brainmaps/backend.ts | 87 +-
src/datasource/brainmaps/base.ts | 4 +-
.../brainmaps/credentials_provider.ts | 2 +-
src/datasource/brainmaps/frontend.ts | 87 +-
.../register_credentials_provider.ts | 19 +-
src/datasource/brainmaps/register_default.ts | 9 +-
src/datasource/deepzoom/async_computation.ts | 2 +
src/datasource/deepzoom/backend.ts | 30 +-
src/datasource/deepzoom/frontend.ts | 55 +-
src/datasource/deepzoom/register_default.ts | 4 +-
src/datasource/default_provider.ts | 13 +-
src/datasource/dvid/api.ts | 11 +-
src/datasource/dvid/async_computation.ts | 1 +
src/datasource/dvid/backend.ts | 44 +-
src/datasource/dvid/credentials_provider.ts | 13 +-
src/datasource/dvid/frontend.ts | 65 +-
.../dvid/register_credentials_provider.ts | 6 +-
src/datasource/dvid/register_default.ts | 4 +-
.../enabled_async_computation_modules.ts | 13 +
src/datasource/enabled_backend_modules.ts | 15 +
src/datasource/enabled_frontend_modules.ts | 19 +
src/datasource/graphene/async_computation.ts | 2 +
src/datasource/graphene/backend.ts | 103 +-
src/datasource/graphene/base.ts | 16 +-
src/datasource/graphene/frontend.ts | 187 +-
src/datasource/graphene/register_default.ts | 4 +-
src/datasource/index.ts | 48 +-
.../middleauth/credentials_provider.ts | 12 +-
.../register_credentials_provider.ts | 6 +-
src/datasource/n5/async_computation.ts | 3 +
src/datasource/n5/backend.ts | 34 +-
src/datasource/n5/frontend.ts | 57 +-
src/datasource/n5/register_default.ts | 4 +-
src/datasource/ngauth/credentials_provider.ts | 16 +-
.../ngauth/register_credentials_provider.ts | 6 +-
.../nggraph/credentials_provider.ts | 6 +-
src/datasource/nggraph/frontend.ts | 47 +-
src/datasource/nggraph/register_default.ts | 4 +-
src/datasource/nifti/async_computation.ts | 1 +
src/datasource/nifti/backend.ts | 62 +-
src/datasource/nifti/base.ts | 2 +-
src/datasource/nifti/frontend.ts | 40 +-
src/datasource/nifti/register_default.ts | 4 +-
src/datasource/obj/async_computation.ts | 1 +
src/datasource/obj/backend.ts | 10 +-
src/datasource/obj/frontend.ts | 12 +-
src/datasource/obj/register_default.ts | 4 +-
.../precomputed/async_computation.ts | 4 +
src/datasource/precomputed/backend.ts | 105 +-
src/datasource/precomputed/base.ts | 9 +-
src/datasource/precomputed/frontend.ts | 94 +-
.../precomputed/register_default.ts | 4 +-
src/datasource/python/backend.ts | 40 +-
src/datasource/python/base.ts | 3 +-
...ntend.spec.ts => frontend.browser_test.ts} | 3 +-
src/datasource/python/frontend.ts | 54 +-
src/datasource/render/async_computation.ts | 1 +
src/datasource/render/backend.ts | 30 +-
src/datasource/render/frontend.ts | 39 +-
src/datasource/render/register_default.ts | 4 +-
src/datasource/state_share.ts | 14 +-
src/datasource/vtk/async_computation.ts | 1 +
src/datasource/vtk/backend.ts | 13 +-
src/datasource/vtk/frontend.ts | 12 +-
src/datasource/vtk/register_default.ts | 4 +-
src/datasource/zarr/async_computation.ts | 3 +
src/datasource/zarr/backend.ts | 40 +-
src/datasource/zarr/base.ts | 2 +-
src/datasource/zarr/codec/blosc/decode.ts | 12 +-
src/datasource/zarr/codec/blosc/resolve.ts | 8 +-
src/datasource/zarr/codec/bytes/decode.ts | 13 +-
src/datasource/zarr/codec/bytes/resolve.ts | 14 +-
src/datasource/zarr/codec/crc32c/decode.ts | 8 +-
src/datasource/zarr/codec/crc32c/resolve.ts | 6 +-
src/datasource/zarr/codec/decode.ts | 14 +-
src/datasource/zarr/codec/gzip/decode.ts | 12 +-
src/datasource/zarr/codec/gzip/resolve.ts | 10 +-
src/datasource/zarr/codec/index.ts | 2 +-
src/datasource/zarr/codec/resolve.ts | 10 +-
.../zarr/codec/sharding_indexed/decode.ts | 27 +-
.../zarr/codec/sharding_indexed/resolve.ts | 14 +-
src/datasource/zarr/codec/transpose/decode.ts | 9 +-
.../zarr/codec/transpose/resolve.ts | 10 +-
src/datasource/zarr/codec/zstd/decode.ts | 12 +-
src/datasource/zarr/codec/zstd/resolve.ts | 8 +-
src/datasource/zarr/frontend.ts | 74 +-
src/datasource/zarr/metadata/index.ts | 6 +-
src/datasource/zarr/metadata/parse.ts | 40 +-
src/datasource/zarr/metadata/parse_util.ts | 41 +
src/datasource/zarr/ome.ts | 10 +-
src/datasource/zarr/register_default.ts | 4 +-
src/display_context.ts | 20 +-
src/gpu_hash/hash_table.benchmark.ts | 11 +-
src/gpu_hash/hash_table.spec.ts | 39 +-
src/gpu_hash/hash_table.ts | 6 +-
...{shader.spec.ts => shader.browser_test.ts} | 34 +-
src/gpu_hash/shader.ts | 15 +-
src/help/input_event_bindings.ts | 23 +-
src/kvstore/index.ts | 2 +-
src/kvstore/special/index.ts | 16 +-
.../annotation/index.ts} | 93 +-
.../annotation/style.css} | 0
src/layer/enabled_frontend_modules.ts | 5 +
.../image/index.ts} | 92 +-
.../image/style.css} | 0
src/{layer.ts => layer/index.ts} | 128 +-
src/{ => layer}/layer_data_source.ts | 35 +-
.../segmentation/index.ts} | 414 +-
src/layer/segmentation/json_keys.ts | 27 +
src/layer/segmentation/layer_controls.ts | 156 +
.../segmentation/style.css} | 0
.../single_mesh/index.ts} | 32 +-
.../single_mesh/style.css} | 0
src/layer_group_viewer.ts | 72 +-
src/layer_groups_layout.ts | 36 +-
src/layout.ts | 4 +-
src/main.bundle.js | 3 +
src/main.ts | 7 +-
src/main_module.ts | 77 +-
src/main_python.ts | 44 +-
src/mesh/backend.ts | 64 +-
src/mesh/draco/index.ts | 41 +-
src/mesh/frontend.ts | 68 +-
src/mesh/multiscale.spec.ts | 7 +-
src/mesh/multiscale.ts | 5 +-
src/mesh/triangle_strips.spec.ts | 3 +-
src/mesh/triangle_strips.ts | 4 +-
src/navigation_state.ts | 28 +-
src/object_picking.ts | 6 +-
src/overlay.ts | 12 +-
src/perspective_view/backend.ts | 16 +-
src/perspective_view/panel.ts | 90 +-
src/perspective_view/render_layer.ts | 18 +-
src/projection_parameters.ts | 10 +-
src/python_integration/api.ts | 16 +-
.../credentials_provider.ts | 16 +-
src/python_integration/event_action_map.ts | 8 +-
src/python_integration/prefetch.ts | 21 +-
src/python_integration/remote_actions.ts | 16 +-
.../remote_status_messages.ts | 10 +-
src/python_integration/screenshots.ts | 39 +-
src/python_integration/volume.ts | 56 +-
src/render_coordinate_transform.ts | 30 +-
src/render_layer_backend.ts | 18 +-
src/render_scale_statistics.ts | 8 +-
src/rendered_data_panel.ts | 49 +-
src/renderlayer.ts | 45 +-
....spec.ts => segment_color.browser_test.ts} | 12 +-
src/segment_color.ts | 24 +-
src/segmentation_display_state/backend.ts | 31 +-
src/segmentation_display_state/base.ts | 14 +-
src/segmentation_display_state/frontend.ts | 71 +-
.../property_map.spec.ts | 5 +-
.../property_map.ts | 35 +-
src/segmentation_graph/local.spec.ts | 21 +-
src/segmentation_graph/local.ts | 22 +-
src/segmentation_graph/segment_id.ts | 2 +-
src/segmentation_graph/source.ts | 27 +-
src/shared_disjoint_sets.ts | 16 +-
src/shared_watchable_value.ts | 7 +-
src/single_mesh/backend.ts | 47 +-
src/single_mesh/base.ts | 2 +-
...ntend.spec.ts => frontend.browser_test.ts} | 21 +-
src/single_mesh/frontend.ts | 77 +-
src/skeleton/backend.ts | 25 +-
src/skeleton/base.ts | 2 +-
src/skeleton/decode_precomputed_skeleton.ts | 14 +-
src/skeleton/decode_swc_skeleton.spec.ts | 5 +-
src/skeleton/decode_swc_skeleton.ts | 4 +-
src/skeleton/frontend.ts | 84 +-
src/sliceview/backend.ts | 52 +-
.../backend_chunk_decoders/bossNpz.ts | 16 +-
.../compressed_segmentation.ts | 4 +-
.../backend_chunk_decoders/compresso.ts | 12 +-
src/sliceview/backend_chunk_decoders/index.ts | 4 +-
src/sliceview/backend_chunk_decoders/jpeg.ts | 10 +-
.../backend_chunk_decoders/ndstoreNpz.ts | 16 +-
src/sliceview/backend_chunk_decoders/png.ts | 12 +-
.../backend_chunk_decoders/postprocess.ts | 10 +-
src/sliceview/backend_chunk_decoders/raw.ts | 13 +-
src/sliceview/base.spec.ts | 7 +-
src/sliceview/base.ts | 24 +-
src/sliceview/bounding_box_shader_helper.ts | 6 +-
src/sliceview/chunk_format_handlers.ts | 4 +-
src/sliceview/chunk_format_testing.ts | 69 +-
src/sliceview/chunk_layout.ts | 4 +-
...t.spec.ts => chunk_format.browser_test.ts} | 19 +-
.../compressed_segmentation/chunk_format.ts | 33 +-
.../compressed_segmentation/decode_uint32.ts | 2 +-
.../compressed_segmentation/decode_uint64.ts | 4 +-
.../encode.benchmark.ts | 26 +-
.../compressed_segmentation/encode_common.ts | 2 +-
.../encode_uint32.spec.ts | 11 +-
.../compressed_segmentation/encode_uint32.ts | 8 +-
.../encode_uint64.spec.ts | 11 +-
.../compressed_segmentation/encode_uint64.ts | 8 +-
src/sliceview/compresso/index.ts | 35 +-
src/sliceview/frontend.ts | 96 +-
src/sliceview/panel.ts | 54 +-
src/sliceview/png/index.ts | 36 +-
src/sliceview/renderlayer.ts | 58 +-
src/sliceview/single_texture_chunk_format.ts | 22 +-
...uncompressed_chunk_format.browser_test.ts} | 13 +-
src/sliceview/uncompressed_chunk_format.ts | 33 +-
src/sliceview/volume/backend.ts | 19 +-
src/sliceview/volume/base.ts | 23 +-
src/sliceview/volume/frontend.ts | 23 +-
src/sliceview/volume/image_renderlayer.ts | 37 +-
src/sliceview/volume/renderlayer.ts | 67 +-
.../volume/segmentation_renderlayer.ts | 40 +-
src/sliceview/wire_frame.ts | 16 +-
src/status.ts | 2 +-
{third_party => src/third_party}/jpgjs/README | 0
.../third_party/jpgjs/jpg.d.ts | 12 +-
src/third_party/jpgjs/jpg.js | 5255 ++++++
.../third_party}/jpgjs/webpack-fix.diff | 0
src/trackable_alpha.ts | 4 +-
src/trackable_blend.ts | 2 +-
src/trackable_boolean.ts | 10 +-
src/trackable_finite_float.ts | 4 +-
src/trackable_value.ts | 25 +-
src/trackable_vec3.ts | 8 +-
src/ui/annotations.ts | 114 +-
src/ui/context_menu.ts | 9 +-
src/ui/default_clipboard_handling.spec.ts | 3 +-
src/ui/default_clipboard_handling.ts | 6 +-
src/ui/default_input_event_bindings.ts | 4 +-
src/ui/default_viewer.ts | 14 +-
src/ui/default_viewer_setup.ts | 17 +-
src/ui/disable_default_actions.ts | 2 +-
src/ui/drag_and_drop.ts | 7 +-
src/ui/layer_bar.ts | 36 +-
src/ui/layer_data_sources_tab.ts | 67 +-
src/ui/layer_drag_and_drop.ts | 18 +-
src/ui/layer_list_panel.ts | 47 +-
src/ui/layer_side_panel.ts | 47 +-
src/ui/layer_side_panel_state.ts | 16 +-
src/ui/minimal_viewer.ts | 25 +-
src/ui/position_drag_and_drop.ts | 11 +-
src/ui/segment_list.ts | 102 +-
src/ui/segment_select_tools.ts | 25 +-
src/ui/segment_split_merge_tools.ts | 48 +-
src/ui/segmentation_display_options_tab.ts | 26 +-
src/ui/selection_details.ts | 31 +-
src/ui/side_panel.ts | 23 +-
src/ui/side_panel_location.ts | 8 +-
src/ui/state_editor.ts | 20 +-
src/ui/statistics.ts | 30 +-
src/ui/title.ts | 4 +-
src/ui/tool.ts | 47 +-
src/ui/url_hash_binding.ts | 19 +-
src/ui/viewer_settings.ts | 28 +-
src/uint64_map.spec.ts | 5 +-
src/uint64_map.ts | 12 +-
src/uint64_ordered_set.ts | 6 +-
src/uint64_set.spec.ts | 5 +-
src/uint64_set.ts | 12 +-
src/util/array.spec.ts | 3 +-
src/util/automatic_focus.ts | 8 +-
src/util/byte_range_http_requests.ts | 13 +-
src/util/cancellation.spec.ts | 22 +-
src/util/cancellation.ts | 2 +-
src/util/clipboard.ts | 2 +-
.../{color.spec.ts => color.browser_test.ts} | 5 +-
src/util/color.ts | 8 +-
src/util/completion.ts | 2 +-
src/util/data_type.ts | 2 +-
src/util/disjoint_sets.spec.ts | 5 +-
src/util/disjoint_sets.ts | 7 +-
src/util/disposable.ts | 2 -
src/util/drag_and_drop.spec.ts | 3 +-
src/util/drag_and_drop.ts | 4 +-
src/util/endian.spec.ts | 3 +-
src/util/event_action_map.ts | 8 +-
src/util/false.ts | 3 +
src/util/float.ts | 2 +-
src/util/float32_to_string.spec.ts | 3 +-
src/util/gcs_bucket_listing.ts | 12 +-
src/util/geom.spec.ts | 5 +-
src/util/geom.ts | 6 +-
src/util/google_oauth2.ts | 34 +-
src/util/google_tag_manager.ts | 16 +
src/util/hash.ts | 2 +-
src/util/http_path_completion.ts | 26 +-
src/util/http_request.ts | 11 +-
src/util/json.spec.ts | 3 +-
src/util/json.ts | 6 +-
src/util/keyboard_bindings.ts | 12 +-
src/util/lerp.ts | 8 +-
src/util/matrix.spec.ts | 3 +-
src/util/matrix.ts | 2 +-
src/util/memoize.ts | 5 +-
src/util/message_list.ts | 2 +-
src/util/mouse_bindings.ts | 10 +-
src/util/npy.spec.ts | 87 +-
src/util/npy.ts | 13 +-
src/util/number_to_string.spec.ts | 3 +-
src/util/numpy_dtype.ts | 4 +-
src/util/pairing_heap.0.ts | 2 +-
src/util/pairing_heap.1.ts | 2 +-
src/util/random.ts | 2 +-
src/util/s3.ts | 8 +-
src/util/s3_bucket_listing.ts | 8 +-
src/util/si_units.spec.ts | 7 +-
src/util/si_units.ts | 2 +-
src/util/signal.spec.ts | 4 +-
src/util/signal.ts | 1 +
src/util/signal_binding_updater.ts | 2 +-
src/util/spatial_units.ts | 5 +-
src/util/special_protocol_request.ts | 32 +-
src/util/touch_bindings.ts | 12 +-
src/util/trackable.ts | 7 +-
src/util/trackable_enum.ts | 6 +-
src/util/true.ts | 3 +
src/util/uint64.spec.ts | 31 +-
src/util/vector.ts | 2 +-
src/util/watchable_map.ts | 4 +-
src/util/zorder.spec.ts | 5 +-
src/util/zorder.ts | 4 +-
src/viewer.ts | 161 +-
src/viewer_state.ts | 14 +-
src/visibility_priority/backend.ts | 10 +-
src/visibility_priority/frontend.ts | 7 +-
src/volume_rendering/backend.ts | 30 +-
src/volume_rendering/base.ts | 14 +-
...ts => volume_render_layer.browser_test.ts} | 7 +-
src/volume_rendering/volume_render_layer.ts | 83 +-
src/webgl/buffer.ts | 11 +-
src/webgl/circles.ts | 4 +-
src/webgl/context.ts | 4 +-
src/webgl/dynamic_shader.ts | 24 +-
src/webgl/ellipse.ts | 3 +-
src/webgl/empirical_cdf.ts | 22 +-
...pec.ts => index_emulation.browser_test.ts} | 5 +-
src/webgl/index_emulation.ts | 10 +-
.../{lerp.spec.ts => lerp.browser_test.ts} | 39 +-
src/webgl/lerp.ts | 15 +-
src/webgl/lines.ts | 6 +-
src/webgl/offscreen.ts | 16 +-
src/webgl/shader.ts | 8 +-
...lib.spec.ts => shader_lib.browser_test.ts} | 43 +-
src/webgl/shader_lib.ts | 12 +-
...spec.ts => shader_testing.browser_test.ts} | 3 +-
src/webgl/shader_testing.ts | 25 +-
....ts => shader_ui_controls.browser_test.ts} | 7 +-
src/webgl/shader_ui_controls.ts | 44 +-
src/webgl/spheres.ts | 9 +-
src/webgl/square_corners_buffer.ts | 6 +-
src/webgl/testing.ts | 12 +-
src/webgl/texture.ts | 2 +-
...spec.ts => texture_access.browser_test.ts} | 34 +-
src/webgl/texture_access.ts | 17 +-
src/webgl/trivial_shaders.ts | 7 +-
src/webgl/vertex_id.ts | 8 +-
src/widget/add_button.ts | 5 +-
src/widget/annotation_tool_status.ts | 21 +-
src/widget/channel_dimensions_widget.ts | 20 +-
src/widget/checkbox_icon.ts | 9 +-
src/widget/close_button.ts | 5 +-
src/widget/color.ts | 10 +-
src/widget/coordinate_transform.ts | 36 +-
src/widget/copy_button.ts | 5 +-
src/widget/delete_button.ts | 5 +-
src/widget/dependent_view_widget.ts | 10 +-
src/widget/display_dimensions_widget.ts | 29 +-
src/widget/enum_widget.ts | 4 +-
src/widget/eye_button.ts | 7 +-
src/widget/filter_button.ts | 5 +-
src/widget/help_button.ts | 3 +-
src/widget/icon.ts | 2 +-
src/widget/invlerp.ts | 72 +-
src/widget/layer_control.ts | 18 +-
src/widget/layer_control_channel_invlerp.ts | 24 +-
src/widget/layer_control_checkbox.ts | 8 +-
src/widget/layer_control_color.ts | 13 +-
src/widget/layer_control_enum.ts | 11 +-
src/widget/layer_control_property_invlerp.ts | 24 +-
src/widget/layer_control_range.ts | 12 +-
src/widget/layer_reference.ts | 9 +-
src/widget/linked_layer.ts | 13 +-
src/widget/maximize_button.ts | 5 +-
src/widget/move_to_button.ts | 3 +-
src/widget/multiline_autocomplete.ts | 33 +-
src/widget/number_input_widget.ts | 7 +-
src/widget/position_plot.ts | 22 +-
src/widget/position_widget.ts | 172 +-
src/widget/range.ts | 8 +-
src/widget/render_scale_widget.ts | 36 +-
src/widget/scale_bar.ts | 24 +-
src/widget/segmentation_color_mode.ts | 20 +-
src/widget/shader_code_widget.ts | 23 +-
src/widget/shader_controls.ts | 45 +-
src/widget/star_button.ts | 7 +-
src/widget/tab_view.ts | 20 +-
src/widget/text_icon_button.ts | 2 +-
src/widget/text_input.ts | 10 +-
src/widget/tooltip.ts | 6 +-
src/widget/vec3_entry_widget.ts | 12 +-
src/widget/virtual_list.ts | 11 +-
src/worker_rpc.ts | 19 +-
src/worker_rpc_context.ts | 2 +-
third_party/jpgjs/jpg.js | 4280 -----
tox.ini | 9 +-
tsconfig.json | 17 +-
typings/binary.d.ts | 4 +
typings/glsl-editor.d.ts | 2 +-
typings/glsl.d.ts | 24 -
typings/html.d.ts | 4 +
typings/index.d.ts | 11 +-
typings/jpgjs.d.ts | 39 -
typings/svg.d.ts | 24 -
typings/url-loader.d.ts | 24 -
vite.config.ts | 76 +
vitest.workspace.ts | 34 +
558 files changed, 41983 insertions(+), 16613 deletions(-)
delete mode 100644 .eslintrc
create mode 100644 .eslintrc.yml
delete mode 100644 biome.json
create mode 100644 build_tools/build-package.ts
create mode 100644 build_tools/cli.ts
create mode 100644 build_tools/postpack.ts
create mode 100644 build_tools/update-conditions.ts
create mode 100644 build_tools/vite/vite-plugin-binary.ts
create mode 100644 build_tools/vite/vite-plugin-dev-server-path-map.ts
delete mode 100644 config/bundle-config.js
delete mode 100644 config/config.js
delete mode 100644 config/esbuild-cli.js
delete mode 100644 config/esbuild-dev-server.js
delete mode 100644 config/esbuild.js
delete mode 100644 config/esbuild_svg_inline_loader.js
rename config/{generate_code.js => generate-code.ts} (100%)
delete mode 100644 config/karma-entry-points.js
delete mode 100644 config/karma.benchmark.js
delete mode 100644 config/karma.conf.js
delete mode 100644 config/resolve_real.js
delete mode 100644 config/static-site-live-server.js
create mode 100644 examples/README.md
create mode 100644 examples/parcel/README.md
create mode 100644 examples/parcel/parcel-project-built/.gitignore
create mode 100644 examples/parcel/parcel-project-built/.parcelrc
create mode 100644 examples/parcel/parcel-project-built/index.html
create mode 100644 examples/parcel/parcel-project-built/package-lock.json
create mode 100644 examples/parcel/parcel-project-built/package.json
create mode 100644 examples/parcel/parcel-project-built/src/index.js
create mode 100644 examples/parcel/parcel-project-built/svgo.config.json
create mode 100644 examples/parcel/parcel-project-source/.gitignore
create mode 100644 examples/parcel/parcel-project-source/.parcelrc
create mode 100644 examples/parcel/parcel-project-source/index.html
create mode 100644 examples/parcel/parcel-project-source/package-lock.json
create mode 100644 examples/parcel/parcel-project-source/package.json
create mode 100644 examples/parcel/parcel-project-source/src/index.js
create mode 100644 examples/parcel/parcel-project-source/svgo.config.json
create mode 100644 examples/parcel/parcel-project-source/tsconfig.json
create mode 100644 examples/vite/vite-project-built/.gitignore
create mode 100644 examples/vite/vite-project-built/index.html
create mode 100644 examples/vite/vite-project-built/package-lock.json
create mode 100644 examples/vite/vite-project-built/package.json
create mode 100644 examples/vite/vite-project-built/vite.config.ts
create mode 100644 examples/vite/vite-project-source/.gitignore
create mode 100644 examples/vite/vite-project-source/index.html
create mode 100644 examples/vite/vite-project-source/package-lock.json
create mode 100644 examples/vite/vite-project-source/package.json
create mode 100644 examples/vite/vite-project-source/vite.config.ts
create mode 100644 examples/webpack/webpack-project-built/.gitignore
create mode 100644 examples/webpack/webpack-project-built/README.md
create mode 100644 examples/webpack/webpack-project-built/package-lock.json
create mode 100644 examples/webpack/webpack-project-built/package.json
create mode 100644 examples/webpack/webpack-project-built/src/index.js
create mode 100644 examples/webpack/webpack-project-built/webpack.config.js
create mode 100644 examples/webpack/webpack-project-source/.gitignore
create mode 100644 examples/webpack/webpack-project-source/package-lock.json
create mode 100644 examples/webpack/webpack-project-source/package.json
create mode 100644 examples/webpack/webpack-project-source/src/index.js
create mode 100644 examples/webpack/webpack-project-source/webpack.config.js
create mode 100644 index.html
create mode 100644 python/requirements-test-browser.txt
create mode 100644 python/requirements-test.txt
create mode 100644 python/tests/client_test.py
rename python/tests/{shader_controls.py => shader_controls_test.py} (100%)
rename src/annotation/{frontend_source.spec.ts => frontend_source.browser_test.ts} (95%)
create mode 100644 src/async_computation.bundle.js
create mode 100644 src/chunk_worker.bundle.js
create mode 100644 src/datasource/boss/async_computation.ts
create mode 100644 src/datasource/brainmaps/async_computation.ts
create mode 100644 src/datasource/deepzoom/async_computation.ts
create mode 100644 src/datasource/dvid/async_computation.ts
create mode 100644 src/datasource/enabled_async_computation_modules.ts
create mode 100644 src/datasource/enabled_backend_modules.ts
create mode 100644 src/datasource/enabled_frontend_modules.ts
create mode 100644 src/datasource/graphene/async_computation.ts
create mode 100644 src/datasource/n5/async_computation.ts
create mode 100644 src/datasource/nifti/async_computation.ts
create mode 100644 src/datasource/obj/async_computation.ts
create mode 100644 src/datasource/precomputed/async_computation.ts
rename src/datasource/python/{frontend.spec.ts => frontend.browser_test.ts} (90%)
create mode 100644 src/datasource/render/async_computation.ts
create mode 100644 src/datasource/vtk/async_computation.ts
create mode 100644 src/datasource/zarr/async_computation.ts
create mode 100644 src/datasource/zarr/metadata/parse_util.ts
rename src/gpu_hash/{shader.spec.ts => shader.browser_test.ts} (86%)
rename src/{annotation/user_layer.ts => layer/annotation/index.ts} (90%)
rename src/{annotation/user_layer.css => layer/annotation/style.css} (100%)
create mode 100644 src/layer/enabled_frontend_modules.ts
rename src/{image_user_layer.ts => layer/image/index.ts} (87%)
rename src/{image_user_layer.css => layer/image/style.css} (100%)
rename src/{layer.ts => layer/index.ts} (96%)
rename src/{ => layer}/layer_data_source.ts (93%)
rename src/{segmentation_user_layer.ts => layer/segmentation/index.ts} (77%)
create mode 100644 src/layer/segmentation/json_keys.ts
create mode 100644 src/layer/segmentation/layer_controls.ts
rename src/{segmentation_user_layer.css => layer/segmentation/style.css} (100%)
rename src/{single_mesh_user_layer.ts => layer/single_mesh/index.ts} (89%)
rename src/{single_mesh_user_layer.css => layer/single_mesh/style.css} (100%)
create mode 100644 src/main.bundle.js
rename src/{segment_color.spec.ts => segment_color.browser_test.ts} (86%)
rename src/single_mesh/{frontend.spec.ts => frontend.browser_test.ts} (84%)
rename src/sliceview/compressed_segmentation/{chunk_format.spec.ts => chunk_format.browser_test.ts} (84%)
rename src/sliceview/{uncompressed_chunk_format.spec.ts => uncompressed_chunk_format.browser_test.ts} (85%)
rename {third_party => src/third_party}/jpgjs/README (100%)
rename typings/karma-benchmark.d.ts => src/third_party/jpgjs/jpg.d.ts (72%)
create mode 100644 src/third_party/jpgjs/jpg.js
rename {third_party => src/third_party}/jpgjs/webpack-fix.diff (100%)
rename src/util/{color.spec.ts => color.browser_test.ts} (96%)
create mode 100644 src/util/false.ts
create mode 100644 src/util/google_tag_manager.ts
create mode 100644 src/util/true.ts
rename src/volume_rendering/{volume_render_layer.spec.ts => volume_render_layer.browser_test.ts} (88%)
rename src/webgl/{index_emulation.spec.ts => index_emulation.browser_test.ts} (91%)
rename src/webgl/{lerp.spec.ts => lerp.browser_test.ts} (94%)
rename src/webgl/{shader_lib.spec.ts => shader_lib.browser_test.ts} (86%)
rename src/webgl/{shader_testing.spec.ts => shader_testing.browser_test.ts} (97%)
rename src/webgl/{shader_ui_controls.spec.ts => shader_ui_controls.browser_test.ts} (98%)
rename src/webgl/{texture_access.spec.ts => texture_access.browser_test.ts} (80%)
delete mode 100644 third_party/jpgjs/jpg.js
create mode 100644 typings/binary.d.ts
delete mode 100644 typings/glsl.d.ts
create mode 100644 typings/html.d.ts
delete mode 100644 typings/jpgjs.d.ts
delete mode 100644 typings/svg.d.ts
delete mode 100644 typings/url-loader.d.ts
create mode 100644 vite.config.ts
create mode 100644 vitest.workspace.ts
diff --git a/.eslintignore b/.eslintignore
index a04eb624b..897be2bcc 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,3 +2,15 @@ node_modules
dist
python
templates/neuroglancer/sliceview
+src/third_party/jpgjs/jpg.js
+templates
+build
+.tox
+.nox
+/lib
+python
+config
+typings
+src/mesh/draco/stub.js
+tsconfig.tsbuildinfo
+/examples
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index b5f5d9003..000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "root": true,
- "parser": "@typescript-eslint/parser",
- "plugins": ["@typescript-eslint"],
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/eslint-recommended",
- "plugin:@typescript-eslint/recommended"
- ],
- "rules": {
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/explicit-module-boundary-types": "off",
- "@typescript-eslint/no-non-null-assertion": "off",
- "@typescript-eslint/no-inferrable-types": "off",
- "@typescript-eslint/no-this-alias": "off",
- "@typescript-eslint/no-empty-function": "off",
- "@typescript-eslint/no-empty-interface": "off"
- }
-}
diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 000000000..0e53e5ca1
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1,67 @@
+root: true
+parser: "@typescript-eslint/parser"
+plugins:
+ - "@typescript-eslint"
+ - "import"
+settings:
+ "import/parsers":
+ "@typescript-eslint/parser": [".ts", ".tsx"]
+ "import/resolver":
+ "typescript":
+ "node":
+extends:
+ - "eslint:recommended"
+ - "plugin:@typescript-eslint/eslint-recommended"
+ - "plugin:@typescript-eslint/recommended"
+ - "plugin:import/recommended"
+rules:
+ "@typescript-eslint/no-explicit-any": "off"
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ "@typescript-eslint/no-non-null-assertion": "off"
+ "@typescript-eslint/no-inferrable-types": "off"
+ "@typescript-eslint/no-this-alias": "off"
+ "@typescript-eslint/no-empty-function": "off"
+ "@typescript-eslint/no-empty-interface": "off"
+ "prefer-const":
+ - "error"
+ - destructuring: "all"
+ "no-constant-condition": "off"
+ "@typescript-eslint/no-unused-vars":
+ - "error"
+ - argsIgnorePattern: "^_"
+ ignoreRestSiblings: true
+ "@typescript-eslint/ban-types":
+ - "error"
+ - types:
+ # unban Function
+ "Function": false
+ extendDefaults: true
+ "no-unsafe-finally": "off"
+ "require-yield": "off"
+ "no-inner-declarations": "off"
+ "import/no-named-as-default-member": "off"
+ "import/no-cycle": "error"
+ "@typescript-eslint/consistent-type-imports": "error"
+ "import/no-unresolved": "error"
+ "import/no-extraneous-dependencies": "error"
+ "import/first": "error"
+ "import/order":
+ - "error"
+ - groups:
+ - "builtin"
+ - "external"
+ - "internal"
+ alphabetize:
+ order: "asc"
+ orderImportKind: "asc"
+overrides:
+ - files:
+ - "src/**/*"
+ rules:
+ "no-restricted-imports":
+ - "error"
+ - patterns:
+ - group:
+ - "./"
+ - "../"
+ message: "Relative imports are not allowed."
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c66803460..80eb36696 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,10 +19,10 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- - uses: actions/cache@v2
- with:
- path: "**/node_modules"
- key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
+ # - uses: actions/cache@v2
+ # with:
+ # path: "**/node_modules"
+ # key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
- run: npm install
- run: npm run format:fix
- name: Check for dirty working directory
@@ -31,23 +31,12 @@ jobs:
- name: Typecheck with TypeScript
run: npm run typecheck
- name: Build client bundles
- run: npm run build -- --no-typecheck
- - name: Build JavaScript module
- run: npm run build-module -- --no-typecheck
+ run: npm run build -- --no-typecheck --no-lint
- name: Build Python client bundles
- run: npm run build-python -- --no-typecheck
+ run: npm run build-python -- --no-typecheck --no-lint
- uses: ./.github/actions/setup-firefox
- name: Run JavaScript tests (including WebGL)
- # Swiftshader, used by Chrome headless, crashes when running Neuroglancer
- # tests.
- #
- # The only reliable headless configuration is Firefox on Linux under
- # xvfb-run, which uses Mesa software rendering.
- if: startsWith(runner.os, 'Linux')
- run: xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' npm run test -- --browsers Firefox
- - name: Run JavaScript tests (excluding WebGL)
- if: ${{ !startsWith(runner.os, 'Linux') }}
- run: npm run test -- --browsers ChromeHeadless --define=NEUROGLANCER_SKIP_WEBGL_TESTS
+ run: npm test
- name: Run JavaScript benchmarks
run: npm run benchmark
@@ -63,6 +52,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
+ - "3.12"
node-version:
- "20.x"
os:
@@ -83,23 +73,23 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- - uses: actions/cache@v2
- with:
- path: "**/node_modules"
- key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
- - name: Get pip cache dir
- id: pip-cache
- run: |
- echo "::set-output name=dir::$(pip cache dir)"
- - uses: actions/cache@v2
- with:
- path: ${{ steps.pip-cache.outputs.dir }}
- key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('setup.py') }}
+ # - uses: actions/cache@v2
+ # with:
+ # path: "**/node_modules"
+ # key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
+ # - name: Get pip cache dir
+ # id: pip-cache
+ # run: |
+ # echo "::set-output name=dir::$(pip cache dir)"
+ # - uses: actions/cache@v2
+ # with:
+ # path: ${{ steps.pip-cache.outputs.dir }}
+ # key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('setup.py') }}
# Uncomment the action below for an interactive shell
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
- name: Install Python packaging/test tools
- run: python -m pip install --upgrade pip tox nox wheel numpy pytest
+ run: python -m pip install --upgrade pip tox nox wheel numpy -r python/requirements-test.txt
- uses: ./.github/actions/setup-firefox
- run: nox -s lint format mypy
- name: Check for dirty working directory
@@ -147,22 +137,22 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: 3.x
- - uses: actions/cache@v2
- with:
- path: "**/node_modules"
- key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
+ # - uses: actions/cache@v2
+ # with:
+ # path: "**/node_modules"
+ # key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- - uses: actions/cache@v2
- with:
- path: ${{ steps.pip-cache.outputs.dir }}
- key: ${{ runner.os }}-buildwheel-${{ hashFiles('setup.py') }}
+ # - uses: actions/cache@v2
+ # with:
+ # path: ${{ steps.pip-cache.outputs.dir }}
+ # key: ${{ runner.os }}-buildwheel-${{ hashFiles('setup.py') }}
- run: npm install
- run: |
build_info="{'tag':'$(git describe --always --tags)', 'url':'https://github.com/google/neuroglancer/commit/$(git rev-parse HEAD)', 'timestamp':'$(date)'}"
- npm run build-python -- --no-typecheck --define NEUROGLANCER_BUILD_INFO="${build_info}"
+ npm run build-python -- --no-typecheck --no-lint --define NEUROGLANCER_BUILD_INFO="${build_info}"
shell: bash
- name: Check for dirty working directory
run: git diff --exit-code
diff --git a/.gitignore b/.gitignore
index 47161fcf7..47de04f74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/node_modules/
+node_modules
/dist/
/trans
/build/
@@ -18,3 +18,5 @@ tsconfig.tsbuildinfo
.nox
.pytest_cache
/ngauth_server/secrets/
+.eslintcache
+/lib
diff --git a/.prettierignore b/.prettierignore
index 9ad83dc42..56db52f7b 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -2,3 +2,6 @@
/python/
/third_party/jpgjs/jpg.js
/testdata/*.json
+.parcel-cache
+dist
+/lib
diff --git a/.prettierrc.yml b/.prettierrc.yml
index e69de29bb..14ff3cbc8 100644
--- a/.prettierrc.yml
+++ b/.prettierrc.yml
@@ -0,0 +1,4 @@
+overrides:
+ - files: ".parcelrc"
+ options:
+ parser: "json5"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fbbb00cd7..808efa17a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@ All submissions, including submissions by project members, require review.
### Coding Style
For consistency, please ensure that all TypeScript/JavaScript files
-are linted with Biome and formatted by `prettier`.
+are linted with `eslint` and formatted by `prettier`.
You can check for lint/format issues with:
diff --git a/MANIFEST.in b/MANIFEST.in
index b356e742a..9e8a9c28a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,6 +5,7 @@ prune python/build_tools
prune config
prune ngauth_server
prune src
+prune build_tools
prune templates
prune third_party
prune typings
@@ -24,9 +25,16 @@ exclude tslint.json
prune .github
prune *.egg-info
exclude tox.ini
+exclude noxfile.py
exclude python/.pylintrc
global-exclude .gitignore
+global-exclude .gitattributes
global-exclude .dockerignore
exclude python/Dockerfile
exclude python/CMakeLists.txt
exclude MANIFEST.in
+exclude *.ts
+exclude .prettierrc.yml
+exclude .prettierignore
+exclude .eslintrc.yml
+exclude index.html
diff --git a/biome.json b/biome.json
deleted file mode 100644
index 30759a3d2..000000000
--- a/biome.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
- "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
- "vcs": {
- "clientKind": "git",
- "enabled": true,
- "useIgnoreFile": true
- },
- "files": {
- "include": [
- "src/**/*.ts",
- "src/**/*.js",
- "config/**/*.ts",
- "config/**/*.js"
- ],
- "ignore": [
- "python/**",
- "templates/**",
- "testdata/*.json",
- "third_party/jpgjs/jpg.js",
- "dist/**",
- ".tox/**",
- ".nox/**",
- "build/**"
- ]
- },
- "organizeImports": {
- "enabled": true
- },
- "formatter": {
- "enabled": false,
- "indentStyle": "space"
- },
- "linter": {
- "enabled": true,
- "rules": {
- "recommended": true,
- "suspicious": {
- "noRedundantUseStrict": "off",
- "noAssignInExpressions": "off",
- "noExplicitAny": "off",
- "noUnsafeDeclarationMerging": "off",
- "noFallthroughSwitchClause": "off",
- "noConfusingVoidType": "off"
- },
- "complexity": {
- "noForEach": "off",
- "noBannedTypes": "off",
- "noStaticOnlyClass": "off",
- "noThisInStatic": "off"
- },
- "style": {
- "noNonNullAssertion": "off",
- "noParameterAssign": "off",
- "useTemplate": "off",
- "useLiteralEnumMembers": "off",
- "useEnumInitializers": "off",
- "noArguments": "off"
- },
- "correctness": {
- "useYield": "off",
- "noUnsafeFinally": "off"
- },
- "performance": {
- "noDelete": "off"
- }
- }
- }
-}
diff --git a/build_tools/build-package.ts b/build_tools/build-package.ts
new file mode 100644
index 000000000..7423e7397
--- /dev/null
+++ b/build_tools/build-package.ts
@@ -0,0 +1,170 @@
+// Builds the library package.
+//
+// This involves transpiling the TypeScript to JavaScript using esbuild.
+//
+// By default, the built package is staged in `dist/package`, with a
+// `package.json` suitable for publishing to the NPM registry.
+//
+// If --inplace is specfied, the package is instead built in-place, and
+// `package.json` is modified in place to be suitable for packing. The original
+// `package.json` is saved as `package.json.prepack` and is restored
+// automatically by `postpack.ts` (which is run as a postpack script).
+
+import fs from "node:fs/promises";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import esbuild from "esbuild";
+import { glob } from "glob";
+import yargs from "yargs";
+
+const __dirname = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
+const rootDir = path.resolve(__dirname, "..");
+
+async function buildPackage(options: { inplace?: boolean }) {
+ const { inplace = false } = options;
+
+ const srcDir = path.resolve(rootDir, "src");
+ const outDir = inplace ? rootDir : path.resolve(rootDir, "dist", "package");
+ const libDir = path.resolve(outDir, "lib");
+
+ const packageJsonPath = path.resolve(rootDir, "package.json");
+
+ if (inplace) {
+ await fs.rm(libDir, { recursive: true, force: true });
+
+ // Save backup that can be restored by `postpack` script.
+ await fs.copyFile(
+ packageJsonPath,
+ path.resolve(rootDir, "package.json.prepack"),
+ );
+ } else {
+ await fs.rm(outDir, { recursive: true, force: true });
+ }
+
+ const typescriptSources = await glob(["**/*.ts"], {
+ cwd: srcDir,
+ ignore: ["**/*.spec.ts", "**/*.browser_test.ts"],
+ nodir: true,
+ });
+
+ await esbuild.build({
+ entryPoints: typescriptSources.map((name) => path.resolve(srcDir, name)),
+ outbase: srcDir,
+ bundle: false,
+ outdir: libDir,
+ });
+
+ const otherSources = await glob(["**/*.{css,js,html,wasm}"], {
+ cwd: srcDir,
+ nodir: true,
+ });
+ await Promise.all(
+ otherSources.map((name) =>
+ fs.copyFile(path.resolve(srcDir, name), path.resolve(libDir, name)),
+ ),
+ );
+
+ if (!inplace) {
+ const otherFiles = ["README.md", "LICENSE"];
+ await Promise.all(
+ otherFiles.map((name) =>
+ fs.copyFile(path.resolve(rootDir, name), path.resolve(outDir, name)),
+ ),
+ );
+ }
+
+ const packageJson = JSON.parse(
+ await fs.readFile(packageJsonPath, {
+ encoding: "utf-8",
+ }),
+ );
+ delete packageJson["devDependencies"];
+ if (inplace) {
+ // Delete all scripts except `postpack`. If the `postpack` script (which is
+ // needed to restore `package.json.prepack`) were removed, then `npm` does
+ // not run it, because it re-reads `package.json` to resolve each script. The residual postpack script won't work, but
+ const { postpack } = packageJson["scripts"];
+ delete packageJson["scripts"];
+ packageJson["scripts"] = { postpack };
+ } else {
+ delete packageJson["private"];
+ delete packageJson["scripts"];
+ delete packageJson["files"];
+ }
+
+ function convertExportMap(map: Record) {
+ for (const [key, value] of Object.entries(map)) {
+ if (typeof value === "string") {
+ map[key] = value
+ .replace(/\.ts$/, ".js")
+ .replace(/^\.\/src\//, "./lib/");
+ } else {
+ convertExportMap(value);
+ }
+ }
+ }
+
+ convertExportMap(packageJson["imports"]);
+ convertExportMap(packageJson["exports"]);
+
+ const outputPackageJson = path.resolve(outDir, "package.json");
+ const tempPackageJsonPath = outputPackageJson + ".tmp";
+ await fs.writeFile(
+ tempPackageJsonPath,
+ JSON.stringify(packageJson, undefined, 2) + "\n",
+ { encoding: "utf-8" },
+ );
+ await fs.rename(tempPackageJsonPath, outputPackageJson);
+}
+
+async function parseArgsAndRunMain() {
+ const argv = await yargs(process.argv.slice(2))
+ .options({
+ inplace: {
+ type: "boolean",
+ default: false,
+ description: "Convert package to built format inplace.",
+ },
+ ["if-not-toplevel"]: {
+ type: "boolean",
+ default: false,
+ description: "Skip building if invoked on a top-level npm repository.",
+ implies: "inplace",
+ },
+ })
+ .strict()
+ .demandCommand(0, 0)
+ .version(false)
+ .help()
+ .parse();
+
+ if (argv.ifNotToplevel) {
+ // https://stackoverflow.com/a/53239387
+
+ // When invoked as a run script, CWD always equals `rootDir`, and
+ // `process.env.INIT_CWD` indicates the CWD of the original command.
+ //
+ // - When running `npm install` with no arguments within a git checkout of
+ // this repository, `INIT_CWD` will be equal to `rootDir` or a descendant.
+ //
+ // - When running `npm install git+...`, `INIT_CWD` will equal the original
+ // path, and `rootDir` will be some cache directory containing the git -
+ // checkout, and will never be an ancestor of `INIT_CWD`.
+ const initCwd = process.env.INIT_CWD,
+ cwd = process.cwd();
+ if (
+ initCwd !== undefined &&
+ (initCwd === cwd || initCwd.startsWith(cwd + path.sep))
+ ) {
+ console.warn(
+ `Not building package due to --if-not-toplevel: cwd=${JSON.stringify(cwd)}, INIT_CWD=${JSON.stringify(initCwd)}`,
+ );
+ return;
+ }
+ }
+ buildPackage({ inplace: argv.inplace });
+}
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) {
+ parseArgsAndRunMain();
+}
diff --git a/build_tools/cli.ts b/build_tools/cli.ts
new file mode 100644
index 000000000..bf736ee2c
--- /dev/null
+++ b/build_tools/cli.ts
@@ -0,0 +1,266 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Command-line interface for building Neuroglancer.
+
+import process from "node:process";
+import { fileURLToPath, pathToFileURL } from "node:url";
+import path from "path";
+import * as vite from "vite";
+import checkerPlugin from "vite-plugin-checker";
+import yargs from "yargs";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+// yargs strips quotes from string values in config objects
+// (https://github.com/yargs/yargs-parser/issues/385). As a workaround, we add
+// in extra quotes.
+// function mungeConfig(config) {
+// if (Array.isArray(config)) {
+// return config.map(mungeConfig);
+// }
+// if (typeof config === "object") {
+// const result = {};
+// for (const key of Object.keys(config)) {
+// result[key] = mungeConfig(config[key]);
+// }
+// return result;
+// }
+// if (typeof config !== "string") {
+// return config;
+// }
+// return `"${config}"`;
+// }
+
+function parseDefines(
+ definesArg:
+ | Record
+ | string
+ | string[]
+ | Record[],
+): Record {
+ const defines: Record = {};
+ if (typeof definesArg === "object" && !Array.isArray(definesArg)) {
+ definesArg = [definesArg];
+ }
+ let defineList = definesArg || [];
+ if (typeof defineList === "string") {
+ defineList = [defineList];
+ }
+ for (const entry of defineList) {
+ if (typeof entry !== "string") {
+ Object.assign(defines, entry);
+ continue;
+ }
+ const splitPoint = entry.indexOf("=");
+ let key;
+ let value;
+ if (splitPoint === -1) {
+ key = entry;
+ value = "true";
+ } else {
+ key = entry.substring(0, splitPoint);
+ value = entry.substring(splitPoint + 1);
+ }
+ defines[key] = value;
+ }
+ for (const key of Object.keys(defines)) {
+ const value = defines[key];
+ if (typeof value !== "string") {
+ defines[key] = JSON.stringify(value);
+ }
+ }
+ return defines;
+}
+
+type Argv = Awaited>;
+
+async function getViteConfig(argv: Argv): Promise {
+ let config: vite.UserConfig = {};
+ for (const configPath of argv.config) {
+ const loadedConfig = (await import(pathToFileURL(configPath).href)).default;
+ config = vite.mergeConfig(config, loadedConfig);
+ }
+
+ const conditions = argv.conditions;
+ if (argv.python) conditions.push("neuroglancer/python");
+ const outDir =
+ (argv.output as string | undefined) ??
+ (argv.python
+ ? path.resolve(
+ __dirname,
+ "..",
+ "python",
+ "neuroglancer",
+ "static",
+ "client",
+ )
+ : undefined);
+ const inlineConfig = {
+ root: path.resolve(__dirname, ".."),
+ base: argv.base,
+ define: argv.define,
+ ...(argv.mode !== undefined ? { mode: argv.mode } : {}),
+ build: {
+ watch: argv.watch ? {} : undefined,
+ outDir,
+ },
+ plugins: [
+ argv.typecheck || argv.lint
+ ? checkerPlugin({
+ typescript: argv.typecheck ?? false,
+ eslint: argv.lint
+ ? {
+ lintCommand: "eslint .",
+ }
+ : undefined,
+ })
+ : undefined,
+ ],
+ resolve: {
+ conditions,
+ },
+ } satisfies vite.UserConfig;
+ return vite.mergeConfig(config, inlineConfig);
+}
+
+function parseArgs() {
+ return yargs(process.argv.slice(2))
+ .options({
+ typecheck: {
+ type: "boolean",
+ default: true,
+ description: "Typecheck the TypeScript code.",
+ },
+ lint: {
+ type: "boolean",
+ default: true,
+ description: "Run eslint.",
+ },
+ python: {
+ type: "boolean",
+ description:
+ "Build Python client, equivalent to --conditions=neuroglancer/python",
+ default: false,
+ },
+ conditions: {
+ type: "array",
+ coerce: (arg: string[]) => arg.map((x) => x.split(",")).flat(),
+ nargs: 1,
+ default: [],
+ description:
+ "Comma-separated list of additional custom Node.js package import/export conditions.",
+ },
+ define: {
+ type: "array",
+ coerce: parseDefines,
+ nargs: 1,
+ default: [],
+ description:
+ "JavaScript global identifiers to define when building. Usage: `--define VARIABLE=EXPR`.",
+ },
+ mode: {
+ description: "Build mode",
+ type: "string",
+ },
+ base: {
+ type: "string",
+ description: "Base path to assume.",
+ },
+ config: {
+ array: true,
+ string: true,
+ nargs: 1,
+ default: [],
+ description:
+ "Additional vite config module to merge into configuration.",
+ },
+ })
+ .command({
+ command: "serve",
+ aliases: ["$0"],
+ describe: "Run the development server.",
+ builder: (parser) =>
+ parser.options({
+ mode: {
+ default: "development",
+ },
+ port: {
+ group: "Development server options",
+ type: "number",
+ nargs: 1,
+ default: 8080,
+ description: "Port number for the development server",
+ },
+ host: {
+ group: "Development server options",
+ type: "string",
+ nargs: 1,
+ description: "Specifies bind address for development server.",
+ default: "localhost",
+ },
+ }),
+ handler: async (argv) => {
+ const server = await vite.createServer(
+ vite.mergeConfig(await getViteConfig(argv), {
+ server: {
+ port: argv.port,
+ host: argv.host,
+ },
+ }),
+ );
+ await server.listen();
+
+ server.printUrls();
+ server.bindCLIShortcuts({ print: true });
+ },
+ })
+ .command({
+ command: "build",
+ describe: "Build the client.",
+ builder: (parser) =>
+ parser.options({
+ output: {
+ group: "Build options",
+ type: "string",
+ nargs: 1,
+ description: "Output directory.",
+ },
+ watch: {
+ type: "boolean",
+ default: false,
+ description: "Watch for changes.",
+ },
+ mode: {
+ default: "production",
+ },
+ }),
+ handler: async (argv) => {
+ await vite.build(vite.mergeConfig(await getViteConfig(argv), {}));
+ },
+ })
+ .strict()
+ .version(false)
+ .help()
+ .parse();
+}
+
+async function parseArgsAndRunMain() {
+ parseArgs();
+}
+if (process.argv[1] === fileURLToPath(import.meta.url)) {
+ parseArgsAndRunMain();
+}
diff --git a/build_tools/postpack.ts b/build_tools/postpack.ts
new file mode 100644
index 000000000..1fa41e8ad
--- /dev/null
+++ b/build_tools/postpack.ts
@@ -0,0 +1,11 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
+const rootDir = path.resolve(__dirname, "..");
+
+await fs.rename(
+ path.resolve(rootDir, "package.json.prepack"),
+ path.resolve(rootDir, "package.json"),
+);
diff --git a/build_tools/update-conditions.ts b/build_tools/update-conditions.ts
new file mode 100644
index 000000000..91b6ebfe5
--- /dev/null
+++ b/build_tools/update-conditions.ts
@@ -0,0 +1,141 @@
+import fs from "node:fs";
+import path from "node:path";
+
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, "..");
+
+const packageJsonPath = path.resolve(rootDir, "package.json");
+
+const packageJson = JSON.parse(
+ await fs.promises.readFile(packageJsonPath, { encoding: "utf-8" }),
+);
+
+const JS_EXT = ".ts";
+
+const NOOP = "./src/util/false" + JS_EXT;
+
+const imports: Record = {};
+imports["#src/third_party/jpgjs/jpg.js"] = "./src/third_party/jpgjs/jpg.js";
+imports["#src/*.js"] = "./src/*.ts";
+imports["#src/*"] = "./src/*";
+imports["#testdata/*"] = "./testdata/*";
+
+const datasourceDir = path.resolve(rootDir, "src", "datasource");
+const layerDir = path.resolve(rootDir, "src", "layer");
+
+const datasources = (
+ await fs.promises.readdir(datasourceDir, { withFileTypes: true })
+)
+ .filter((e) => e.isDirectory())
+ .map((e) => e.name);
+
+const layers = (await fs.promises.readdir(layerDir, { withFileTypes: true }))
+ .filter((e) => e.isDirectory())
+ .map((e) => e.name);
+
+const datasourceKeys = {
+ backend: "backend",
+ async_computation: "async_computation",
+ register_default: "frontend",
+ register_credentials_provider: "frontend",
+} as const;
+
+const datasourceModules = Object.fromEntries(
+ Object.values(datasourceKeys).map((key) => [key, new Array()]),
+);
+
+for (const datasource of datasources) {
+ for (const [filePrefix, moduleKind] of Object.entries(datasourceKeys)) {
+ const sourcePrefix = `./src/datasource/${datasource}/${filePrefix}`;
+ if (
+ await fs.promises
+ .stat(path.resolve(rootDir, `${sourcePrefix}.ts`))
+ .catch(() => undefined)
+ ) {
+ const source = sourcePrefix + JS_EXT;
+ const conditions: Record = {};
+ if (datasource === "python") {
+ conditions["neuroglancer/python"] = source;
+ conditions.default = NOOP;
+ } else {
+ if (filePrefix === "register_credentials_provider") {
+ conditions["neuroglancer/python"] = NOOP;
+ }
+ conditions[`neuroglancer/datasource/${datasource}:enabled`] = source;
+ conditions["neuroglancer/datasource:none_by_default"] = NOOP;
+ conditions[`neuroglancer/datasource/${datasource}:disabled`] = source;
+ conditions.default = source;
+ }
+ const moduleId = `#datasource/${datasource}/${filePrefix}`;
+ imports[moduleId] = conditions;
+ datasourceModules[moduleKind].push(moduleId);
+ }
+ }
+}
+
+for (const layer of layers) {
+ const source = `./src/layer/${layer}/index` + JS_EXT;
+ imports[`#layer/${layer}`] = {
+ [`neuroglancer/layer/${layer}:enabled`]: source,
+ "neuroglancer/layer:none_by_default": NOOP,
+ [`neuroglancer/layer/${layer}:enabled`]: source,
+ default: source,
+ };
+}
+
+// main entrypoint.
+imports["#main"] = {
+ "neuroglancer/python": "./src/main_python" + JS_EXT,
+ default: "./src/main" + JS_EXT,
+};
+
+// main entrypoint.
+imports["#python_integration_build"] = {
+ "neuroglancer/python": "./src/util/true" + JS_EXT,
+ default: NOOP,
+};
+
+async function writeModule(modulePath: string, imports: string[]) {
+ await fs.promises.writeFile(
+ modulePath,
+ "// DO NOT EDIT: Generated by config/update_conditions.ts\n" +
+ imports.map((name) => `import ${JSON.stringify(name)};\n`).join(""),
+ { encoding: "utf-8" },
+ );
+}
+
+for (const [moduleKind, moduleIds] of Object.entries(datasourceModules)) {
+ await writeModule(
+ path.resolve(
+ rootDir,
+ "src",
+ "datasource",
+ `enabled_${moduleKind}_modules.ts`,
+ ),
+ moduleIds,
+ );
+}
+
+await writeModule(
+ path.resolve(rootDir, "src", "layer", "enabled_frontend_modules.ts"),
+ layers.map((name) => `#layer/${name}`),
+);
+
+packageJson.imports = imports;
+
+packageJson.exports = {
+ ".": "./src/main_module.ts",
+ "./*.js": "./src/*.ts",
+ "./*": "./src/*",
+};
+
+const tempPackageJsonPath = packageJsonPath + ".tmp";
+
+await fs.promises.writeFile(
+ tempPackageJsonPath,
+ JSON.stringify(packageJson, undefined, 2) + "\n",
+ { encoding: "utf-8" },
+);
+await fs.promises.rename(tempPackageJsonPath, packageJsonPath);
diff --git a/build_tools/vite/vite-plugin-binary.ts b/build_tools/vite/vite-plugin-binary.ts
new file mode 100644
index 000000000..183917216
--- /dev/null
+++ b/build_tools/vite/vite-plugin-binary.ts
@@ -0,0 +1,35 @@
+import { readFile } from "fs/promises";
+import type { FilterPattern } from "@rollup/pluginutils";
+import { createFilter } from "@rollup/pluginutils";
+import type { PluginContext } from "rollup";
+import type { Plugin } from "vite";
+
+export default (
+ options: { include?: FilterPattern; exclude?: FilterPattern } = {},
+): Plugin => {
+ const { include, exclude } = options;
+ const filter = createFilter(include, exclude);
+ return {
+ name: "neuroglancer:binary",
+ async load(this: PluginContext, id: string) {
+ if (id[0] === "\0") {
+ // Ignore per rollup convention.
+ return undefined;
+ }
+ const file = id.replace(/[?#].*$/, "");
+ if (!filter(file)) return undefined;
+ this.addWatchFile(file);
+ const encoded = await readFile(file, { encoding: "base64" });
+ const text = `
+const s = atob(${JSON.stringify(encoded)});
+const length = s.length;
+const buffer = new Uint8Array(length);
+for (let i = 0; i < length; ++i) {
+ buffer[i] = s.charCodeAt(i);
+}
+export default buffer;
+`;
+ return text;
+ },
+ };
+};
diff --git a/build_tools/vite/vite-plugin-dev-server-path-map.ts b/build_tools/vite/vite-plugin-dev-server-path-map.ts
new file mode 100644
index 000000000..50a0b71ed
--- /dev/null
+++ b/build_tools/vite/vite-plugin-dev-server-path-map.ts
@@ -0,0 +1,18 @@
+import type { Plugin } from "vite";
+
+export default (options: { map: Map }): Plugin => {
+ const { map } = options;
+ return {
+ name: "neuroglancer-dev-server-path-map",
+ configureServer(server) {
+ server.middlewares.use((req, _res, next) => {
+ const dest = map.get(req.url!);
+ if (dest !== undefined) {
+ req.url = dest;
+ //req.path = dest;
+ }
+ next();
+ });
+ },
+ };
+};
diff --git a/config/bundle-config.js b/config/bundle-config.js
deleted file mode 100644
index 784702761..000000000
--- a/config/bundle-config.js
+++ /dev/null
@@ -1,257 +0,0 @@
-/**
- * @license
- * Copyright 2016-2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-const resolveReal = require("./resolve_real");
-
-const DEFAULT_DATA_SOURCES = (exports.DEFAULT_DATA_SOURCES = [
- {
- source: "#/datasource/brainmaps",
- registerCredentials: "#/datasource/brainmaps/register_credentials_provider",
- asyncComputation: ["#/async_computation/decode_jpeg"],
- },
- {
- source: "#/datasource/boss",
- registerCredentials: "#/datasource/boss/register_credentials_provider",
- asyncComputation: [
- "#/async_computation/decode_jpeg",
- "#/async_computation/decode_gzip",
- ],
- },
- {
- source: "#/datasource/deepzoom",
- asyncComputation: [
- "#/async_computation/decode_jpeg",
- "#/async_computation/decode_png",
- ],
- },
- {
- source: "#/datasource/dvid",
- registerCredentials: "#/datasource/dvid/register_credentials_provider",
- asyncComputation: ["#/async_computation/decode_jpeg"],
- },
- {
- source: "#/datasource/render",
- asyncComputation: ["#/async_computation/decode_jpeg"],
- },
- {
- source: "#/datasource/precomputed",
- asyncComputation: [
- "#/async_computation/decode_jpeg",
- "#/async_computation/decode_gzip",
- "#/async_computation/decode_compresso",
- "#/async_computation/decode_png",
- ],
- },
- {
- source: "#/datasource/nifti",
- asyncComputation: ["#/async_computation/decode_gzip"],
- },
- {
- source: "#/datasource/n5",
- asyncComputation: [
- "#/async_computation/decode_gzip",
- "#/async_computation/decode_blosc",
- ],
- },
- {
- source: "#/datasource/zarr",
- asyncComputation: [
- "#/async_computation/decode_gzip",
- "#/async_computation/decode_blosc",
- "#/async_computation/decode_zstd",
- ],
- },
- // '#/datasource/computed',
- // '#/datasource/computed/example',
- // '#/datasource/computed/tensorflow',
- {
- source: "#/datasource/vtk",
- asyncComputation: ["#/async_computation/vtk_mesh"],
- },
- {
- source: "#/datasource/obj",
- asyncComputation: ["#/async_computation/obj_mesh"],
- },
- {
- source: "#/datasource/ngauth",
- frontend: null,
- backend: null,
- register: null,
- registerCredentials: "#/datasource/ngauth/register_credentials_provider",
- },
- {
- source: "#/datasource/middleauth",
- frontend: null,
- backend: null,
- register: null,
- registerCredentials:
- "#/datasource/middleauth/register_credentials_provider",
- },
- {
- source: "#/datasource/nggraph",
- },
- {
- source: "#/datasource/graphene",
- asyncComputation: [
- "#/async_computation/decode_jpeg",
- "#/async_computation/decode_gzip",
- ],
- },
-]);
-
-const DEFAULT_SUPPORTED_LAYERS = (exports.DEFAULT_SUPPORTED_LAYERS = [
- "#/image_user_layer",
- "#/segmentation_user_layer",
- "#/single_mesh_user_layer",
- "#/annotation/user_layer",
-]);
-
-function getBundleSources(options) {
- const dataSources = [
- ...(options.dataSources || DEFAULT_DATA_SOURCES),
- ...(options.extraDataSources || []),
- ];
- const supportedLayers = options.supportedLayers || DEFAULT_SUPPORTED_LAYERS;
- const frontendDataSourceModules = [];
- const backendDataSourceModules = [];
- const asyncComputationDataSourceModules = new Set();
- const registerCredentials =
- options.registerCredentials !== undefined
- ? options.registerCredentials
- : !options.python;
- for (let datasource of dataSources) {
- if (typeof datasource === "string") {
- datasource = { source: datasource };
- }
- if (datasource.frontend !== null) {
- frontendDataSourceModules.push(
- datasource.frontend || `${datasource.source}/frontend`,
- );
- }
- if (registerCredentials && datasource.registerCredentials) {
- frontendDataSourceModules.push(datasource.registerCredentials);
- }
- if (datasource.register === undefined) {
- frontendDataSourceModules.push(`${datasource.source}/register_default`);
- } else if (datasource.register !== null) {
- frontendDataSourceModules.push(datasource.register);
- }
- if (datasource.backend !== null) {
- backendDataSourceModules.push(
- datasource.backend || `${datasource.source}/backend`,
- );
- }
- if (datasource.asyncComputation !== undefined) {
- for (const m of datasource.asyncComputation) {
- asyncComputationDataSourceModules.add(m);
- }
- }
- }
- const defaultDefines = {
- // This is the default client ID used for the hosted neuroglancer.
- // In addition to the hosted neuroglancer origin, it is valid for
- // the origins:
- //
- // localhost:8000
- // 127.0.0.1:8000
- // localhost:8080
- // 127.0.0.1:8080
- //
- // To deploy to a different origin, you will need to generate your
- // own client ID from on the Google Developer Console and substitute
- // it in.
- BRAINMAPS_CLIENT_ID: JSON.stringify(
- "639403125587-4k5hgdfumtrvur8v48e3pr7oo91d765k.apps.googleusercontent.com",
- ),
-
- "process.env.NODE_ENV": JSON.stringify("production"),
-
- global: "window",
- };
- const extraDefines = options.defines || {};
- const srcDir = resolveReal(__dirname, "..", "src");
- const extraChunkWorkerModules = options.chunkWorkerModules || [];
- const extraAsyncComputationModules = options.asyncComputationModules || [];
- const chunkWorkerModules = [
- "#/worker_rpc_context",
- "#/chunk_manager/backend",
- "#/sliceview/backend",
- "#/perspective_view/backend",
- "#/volume_rendering/backend",
- "#/annotation/backend",
- ...backendDataSourceModules,
- ...extraChunkWorkerModules,
- ];
- const asyncComputationModules = [
- "#/async_computation/handler",
- "#/async_computation/encode_compressed_segmentation",
- ...asyncComputationDataSourceModules,
- ...extraAsyncComputationModules,
- ];
- const frontendModules = options.frontendModules || [
- resolveReal(srcDir, "main.ts"),
- ];
- const frontendLayerModules = [];
- for (const name of supportedLayers) {
- frontendLayerModules.push(name);
- }
-
- return {
- main: [
- ...frontendDataSourceModules,
- ...frontendLayerModules,
- ...frontendModules,
- ],
- workers: {
- chunk_worker: chunkWorkerModules,
- async_computation: asyncComputationModules,
- },
- defines: { ...defaultDefines, ...extraDefines },
- };
-}
-exports.getBundleSources = getBundleSources;
-
-function makePythonClientOptions(options) {
- const srcDir = resolveReal(__dirname, "..", "src");
- options = Object.assign({}, options);
- options.extraDataSources = [
- ...(options.extraDataSources || []),
- { source: "#/datasource/python", register: null },
- ];
- options.frontendModules = options.frontendModules || [
- resolveReal(srcDir, "main_python.ts"),
- ];
- options.registerCredentials = false;
- options.defines = Object.assign(options.defines || {}, {
- NEUROGLANCER_PYTHON_INTEGRATION: "true",
- });
- return options;
-}
-
-exports.makePythonClientOptions = makePythonClientOptions;
-
-exports.getViewerOptions = (baseConfig, options = {}) => {
- if (options.python) {
- baseConfig = makePythonClientOptions(baseConfig);
- }
- if (options.module) {
- const srcDir = resolveReal(__dirname, "..", "src");
- baseConfig.frontendModules = [resolveReal(srcDir, "main_module.ts")];
- }
- return baseConfig;
-};
diff --git a/config/config.js b/config/config.js
deleted file mode 100644
index 2b9bf04b1..000000000
--- a/config/config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// Add in additional command-line build options here.
-module.exports = {
- define: {
- // NEUROGLANCER_CREDIT_LINK: JSON.stringify({url: '...', text: '...'}),
- // NEUROGLANCER_DEFAULT_STATE_FRAGMENT: JSON.stringify('gs://bucket/state.json'),
- // NEUROGLANCER_SHOW_LAYER_BAR_EXTRA_BUTTONS: true,
- // NEUROGLANCER_SHOW_OBJECT_SELECTION_TOOLTIP: true
- },
- // googleTagManager: 'GTM-XXXXXX',
-};
diff --git a/config/esbuild-cli.js b/config/esbuild-cli.js
deleted file mode 100644
index 475366b8a..000000000
--- a/config/esbuild-cli.js
+++ /dev/null
@@ -1,234 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Command-line interface for building Neuroglancer.
-
-"use strict";
-
-const { Builder } = require("./esbuild");
-const path = require("path");
-const fs = require("fs");
-
-// yargs strips quotes from string values in config objects
-// (https://github.com/yargs/yargs-parser/issues/385). As a workaround, we add
-// in extra quotes.
-function mungeConfig(config) {
- if (Array.isArray(config)) {
- return config.map(mungeConfig);
- }
- if (typeof config === "object") {
- const result = {};
- for (const key of Object.keys(config)) {
- result[key] = mungeConfig(config[key]);
- }
- return result;
- }
- if (typeof config !== "string") {
- return config;
- }
- return `"${config}"`;
-}
-
-function parseDefines(definesArg) {
- const defines = {};
- if (typeof definesArg === "object" && !Array.isArray(definesArg)) {
- definesArg = [definesArg];
- }
- let defineList = definesArg || [];
- if (typeof defineList === "string") {
- defineList = [defineList];
- }
- for (const entry of defineList) {
- if (typeof entry !== "string") {
- Object.assign(defines, entry);
- continue;
- }
- const splitPoint = entry.indexOf("=");
- let key;
- let value;
- if (splitPoint === -1) {
- key = entry;
- value = "true";
- } else {
- key = entry.substring(0, splitPoint);
- value = entry.substring(splitPoint + 1);
- }
- defines[key] = value;
- }
- for (const key of Object.keys(defines)) {
- const value = defines[key];
- if (typeof value !== "string") {
- defines[key] = JSON.stringify(value);
- }
- }
- return defines;
-}
-exports.parseDefines = parseDefines;
-
-async function main(argv) {
- let minify = true;
- let python = false;
- let moduleBuild = false;
- let outDir = argv.output;
- const id = argv.config;
- const pythonOutDir =
- argv.output !== undefined
- ? argv.output
- : path.resolve(__dirname, "..", "python", "neuroglancer", "static");
-
- switch (id) {
- case "min":
- break;
- case "dev":
- minify = false;
- break;
- case "python-min":
- python = true;
- outDir = pythonOutDir;
- break;
- case "python-dev":
- python = true;
- minify = false;
- outDir = pythonOutDir;
- break;
- case "module":
- minify = false;
- moduleBuild = true;
- break;
- default:
- throw new Error(`Unsupported config: ${id}`);
- }
- const skipTypeCheck = !argv.typecheck;
- const builder = new Builder({
- outDir,
- id,
- minify,
- python,
- module: moduleBuild,
- define: argv.define,
- inject: argv.inject,
- googleTagManager: argv.googleTagManager,
- analyze: argv.analyze,
- });
- if (moduleBuild && argv.output === undefined) {
- try {
- if ((await fs.promises.lstat(builder.outDir)).isDirectory()) {
- await fs.promises.rm(builder.outDir, { recursive: true });
- }
- } catch (e) {
- // Ignore errors.
- }
- } else {
- await builder.clearOutput();
- }
- if (argv.watch) {
- await require("./esbuild-dev-server")(builder, {
- serve: argv.serve,
- host: argv.host,
- port: argv.port,
- skipTypeCheck,
- });
- } else {
- await builder.buildOrExit({ skipTypeCheck });
- }
-}
-if (require.main === module) {
- const argv = require("yargs")
- .options({
- config: {
- description: "Build configuration identifier",
- type: "string",
- nargs: 1,
- choices: ["min", "dev", "python-min", "python-dev", "module"],
- default: "min",
- },
- analyze: {
- type: "boolean",
- default: false,
- description: "Print bundle analysis.",
- },
- typecheck: {
- type: "boolean",
- default: true,
- description: "Typecheck the TypeScript code.",
- },
- define: {
- type: "array",
- coerce: parseDefines,
- default: [],
- description:
- "JavaScript global identifiers to define when building. Usage: `--define VARIABLE=EXPR`.",
- },
- inject: {
- type: "array",
- default: [],
- description: "Additional modules to inject into global scope.",
- },
- watch: {
- type: "boolean",
- default: false,
- description: "Watch sources for changes and rebuild automatically.",
- },
- serve: {
- group: "Development server options:",
- type: "boolean",
- default: false,
- description: "Run a development server.",
- },
- host: {
- group: "Development server options:",
- type: "string",
- nargs: 1,
- description:
- "Specifies bind address for development server, e.g. 0.0.0.0 or 127.0.0.1",
- default: "127.0.0.1",
- },
- port: {
- group: "Development server options:",
- type: "number",
- nargs: 1,
- default: 8080,
- description: "Port number for the development server",
- },
- configfile: {
- config: true,
- description: "Additional JSON/JavaScript config file to load.",
- configParser: (x) => mungeConfig(require(x)),
- },
- output: {
- type: "string",
- nargs: 1,
- description: "Output directory.",
- },
- "google-tag-manager": {
- group: "Customization",
- type: "string",
- nargs: 1,
- description: "Google tag manager id to include in index.html",
- },
- })
- .strict()
- .config(mungeConfig(require("./config.js")))
- .demandCommand(0, 0)
- .version(false)
- .env("NEUROGLANCER")
- .help()
- .parse();
- if (argv.serve) {
- argv.watch = true;
- }
- main(argv);
-}
diff --git a/config/esbuild-dev-server.js b/config/esbuild-dev-server.js
deleted file mode 100644
index f6e8b123d..000000000
--- a/config/esbuild-dev-server.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Live reload development server for static site bundled using esbuild
-
-"use strict";
-
-const chokidar = require("chokidar");
-const LiveServer = require("./static-site-live-server");
-const path = require("path");
-
-async function tryBuild(builder) {
- try {
- await builder.build();
- return true;
- } catch (e) {
- console.log(`Build failed: ${e}`, e.stack);
- return false;
- }
-}
-
-async function main(builder, options) {
- if (!options.skipTypeCheck) {
- builder.typeCheckWatch();
- }
- await tryBuild(builder);
-
- const { serve = false } = options;
-
- let liveServer = undefined;
- if (serve) {
- liveServer = new LiveServer();
- liveServer.start({
- host: options.host,
- port: options.port,
- root: builder.outDir,
- file: "index.html",
- });
- }
-
- let building = false;
- let needBuild = false;
- const sourceWatcher = chokidar
- .watch(path.resolve(builder.srcDir, "**"))
- .on("change", async (filePath) => {
- if (building) {
- needBuild = true;
- return;
- }
- do {
- needBuild = false;
- try {
- const result = await tryBuild(builder);
- if (serve) {
- if (result) {
- liveServer.reload();
- } else {
- console.log("Not reloading due to errors");
- }
- }
- } catch (e) {
- console.log(`Error building: ${e.message}`);
- }
- } while (needBuild);
- building = false;
- });
-}
-module.exports = main;
diff --git a/config/esbuild.js b/config/esbuild.js
deleted file mode 100644
index b9ce7063d..000000000
--- a/config/esbuild.js
+++ /dev/null
@@ -1,327 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// esbuild/typescript configuration and launcher for Neuroglancer.
-
-"use strict";
-
-const svgInlineLoader = require("./esbuild_svg_inline_loader");
-
-const esbuild = require("esbuild");
-const path = require("path");
-const fs = require("fs");
-const bundleConfig = require("./bundle-config");
-const { spawn } = require("child_process");
-
-function getEntryPointContents(sources) {
- return sources.map((path) => `import ${JSON.stringify(path)};\n`).join("");
-}
-
-const getEntryPointPlugin = (entryPointSources) => {
- const entryPointResolveDir = path.resolve(__dirname, "..");
- const plugin = {
- name: "entrypoint",
- setup(build) {
- build.onResolve({ filter: /^neuroglancer_entrypoint\// }, (args) => ({
- path: args.path.substr("neuroglancer_entrypoint/".length),
- namespace: "neuroglancer_entrypoint",
- }));
- build.onLoad(
- { filter: /.*/, namespace: "neuroglancer_entrypoint" },
- (args) => {
- const m = args.path.match(/(.*)\.bundle.js$/);
- if (m === null) return undefined;
- const entryPoint = m[1];
- const sources = entryPointSources[entryPoint];
- if (sources === undefined) return undefined;
- return {
- contents: getEntryPointContents(sources),
- resolveDir: entryPointResolveDir,
- };
- },
- );
- },
- };
- const entryPoints = Object.keys(entryPointSources).map(
- (name) => `neuroglancer_entrypoint/${name}.bundle.js`,
- );
- return { plugin, entryPoints };
-};
-exports.getEntryPointPlugin = getEntryPointPlugin;
-
-function getCommonPlugins() {
- return [svgInlineLoader({ removeSVGTagAttrs: false, removeTags: true })];
-}
-exports.getCommonPlugins = getCommonPlugins;
-
-class Builder {
- constructor(options = {}) {
- const { id = "min" } = options;
- const {
- outDir = path.resolve(__dirname, "..", "dist", id),
- python = false,
- module: moduleBuild = false,
- define = {},
- inject = [],
- minify = true,
- googleTagManager = undefined,
- analyze = false,
- } = options;
- this.outDir = outDir;
- this.cacheId = id;
- const viewerConfig = bundleConfig.getViewerOptions(
- {},
- {
- python,
- module: moduleBuild,
- },
- );
- this.module = options.module;
- this.bundleSources = bundleConfig.getBundleSources(viewerConfig);
- this.minify = minify;
- this.python = options.python;
- this.srcDir = path.resolve(__dirname, "..", "src");
- this.plugins = getCommonPlugins();
- this.define = define;
- this.inject = inject;
- this.googleTagManager = googleTagManager;
- this.analyze = analyze;
- }
-
- // Deletes .js/.css/.html files from `this.outDir`. Can safely be used on
- // `python/neuroglancer/static` directory.
- async clearOutput() {
- try {
- const pattern = /\.(js|js\.map|html|css)$/;
- const paths = await fs.promises.readdir(this.outDir);
- for (const filename of paths) {
- const p = path.resolve(this.outDir, filename);
- if (!pattern.test(p)) continue;
- try {
- await fs.promises.unlink(p);
- } catch {
- // Ignore errors removing output files
- }
- }
- } catch {
- // ignore errors listing output directory (e.g. if it does not already exist)
- }
- }
-
- async writeIndex() {
- let indexHtml = `
-
-
-
- neuroglancer
-
-`;
-
- const { googleTagManager } = this;
- if (googleTagManager) {
- indexHtml += `
-
-
-`;
- }
-
- indexHtml += `
-
-
-
-
-
-`;
-
- await fs.promises.writeFile(
- path.resolve(this.outDir, "index.html"),
- indexHtml,
- );
- }
-
- getBaseEsbuildConfig(entryPointSources) {
- const { plugin: entryPointPlugin, entryPoints } =
- getEntryPointPlugin(entryPointSources);
- return {
- outdir: this.outDir,
- define: { ...this.bundleSources.defines, ...this.define },
- inject: this.inject,
- minify: this.minify,
- target: "es2019",
- plugins: [entryPointPlugin, ...this.plugins],
- entryPoints,
- loader: { ".wasm": "dataurl" },
- // TODO(jbms): Remove this workaround once evanw/esbuild#1202 is fixed.
- banner: {
- js: "function require(x) { throw new Error('Cannot require ' + x) }",
- },
- };
- }
-
- getWorkerEntrypoints() {
- return this.bundleSources.workers;
- }
-
- getMainEntrypoint() {
- return { main: this.bundleSources.main };
- }
-
- async build() {
- const startTime = Date.now();
- try {
- await fs.promises.mkdir(this.outDir, { recursive: true });
- if (this.module) {
- await this.buildModule();
- } else {
- await this.buildNonModule();
- }
- } catch (e) {
- console.log(`Error: ${e.message}`);
- throw e;
- } finally {
- console.log(`Built in ${(Date.now() - startTime) / 1000.0} seconds`);
- }
- }
-
- async buildNonModule() {
- await this.writeIndex();
- if (!this.python) {
- await fs.promises.copyFile(
- path.resolve(this.srcDir, "datasource/boss/bossauth.html"),
- path.resolve(this.outDir, "bossauth.html"),
- );
- await fs.promises.copyFile(
- path.resolve(this.srcDir, "util/google_oauth2_redirect.html"),
- path.resolve(this.outDir, "google_oauth2_redirect.html"),
- );
- }
- const result = await esbuild.build({
- ...this.getBaseEsbuildConfig({
- ...this.getMainEntrypoint(),
- ...this.getWorkerEntrypoints(),
- }),
- bundle: true,
- sourcemap: true,
- metafile: true,
- });
- if (this.analyze) {
- console.log(await esbuild.analyzeMetafile(result.metafile));
- }
- }
-
- async buildModule() {
- await fs.promises.rm(this.outDir, { recursive: true });
- const { outDir } = this;
- // Build workers and main bundle. The main bundle won't be saved, it is
- // just to analyze dependencies and to generate the CSS bundle.
- const [mainBuildResult, workerBuildResult] = await Promise.all([
- esbuild.build({
- ...this.getBaseEsbuildConfig(this.getMainEntrypoint()),
- bundle: true,
- write: false,
- metafile: true,
- }),
- esbuild.build({
- ...this.getBaseEsbuildConfig(this.getWorkerEntrypoints()),
- bundle: true,
- }),
- ]);
- const metaEntry = mainBuildResult.metafile;
- const cssEntry = mainBuildResult.outputFiles.find((entry) =>
- entry.path.endsWith(".css"),
- ).contents;
- await fs.promises.writeFile(
- path.resolve(this.outDir, "main.css"),
- cssEntry,
- );
- const srcDirPrefix = this.srcDir + path.sep;
- const dependencies = Object.keys(metaEntry.inputs).filter((x) =>
- x.startsWith("src/"),
- );
- const buildResult = await esbuild.build({
- ...this.getBaseEsbuildConfig({}),
- entryPoints: dependencies,
- bundle: false,
- write: false,
- format: "esm",
- });
- for (const entry of buildResult.outputFiles) {
- if (entry.path.endsWith(".css")) continue;
- await fs.promises.mkdir(path.dirname(entry.path), { recursive: true });
- await fs.promises.writeFile(entry.path, entry.contents);
- }
- }
-
- async typeCheck() {
- const startTime = Date.now();
- try {
- await new Promise((resolve, reject) => {
- const child = spawn(
- process.execPath,
- [require.resolve("typescript/lib/tsc.js"), "--noEmit"],
- { stdio: "inherit" },
- );
- child.on("close", (code) => {
- if (code === 0) {
- resolve();
- } else {
- reject(new Error(`tsc exited with code: ${code}`));
- }
- });
- });
- } finally {
- console.log(
- `Type checked in ${(Date.now() - startTime) / 1000.0} seconds`,
- );
- }
- }
-
- typeCheckWatch() {
- const child = spawn(
- process.execPath,
- [
- require.resolve("typescript/lib/tsc.js"),
- "--noEmit",
- "--watch",
- "--preserveWatchOutput",
- ],
- { stdio: "inherit" },
- );
- }
-
- async buildAndTypeCheck(options) {
- const buildPromise = this.build();
- if (!options.skipTypeCheck) {
- await this.typeCheck();
- }
- await buildPromise;
- }
-
- async buildOrExit(options) {
- try {
- await this.buildAndTypeCheck(options);
- } catch (e) {
- process.exit(1);
- }
- }
-}
-exports.Builder = Builder;
diff --git a/config/esbuild_svg_inline_loader.js b/config/esbuild_svg_inline_loader.js
deleted file mode 100644
index 1f11732d8..000000000
--- a/config/esbuild_svg_inline_loader.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// esbuild equivalent of webpack's svg-inline-loader
-
-const fs = require("fs");
-
-module.exports = (options) => {
- const getExtractedSVG = require("svg-inline-loader").getExtractedSVG;
- return {
- name: "svg-inline-loader",
- setup(build) {
- build.onLoad({ filter: /.*\.svg$/, namespace: "file" }, async (args) => {
- const text = await fs.promises.readFile(args.path, "utf8");
- const converted = getExtractedSVG(text, options);
- return {
- contents: `export default ${JSON.stringify(converted)};`,
- };
- });
- },
- };
-};
diff --git a/config/generate_code.js b/config/generate-code.ts
similarity index 100%
rename from config/generate_code.js
rename to config/generate-code.ts
diff --git a/config/karma-entry-points.js b/config/karma-entry-points.js
deleted file mode 100644
index 42d2e4a13..000000000
--- a/config/karma-entry-points.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google Inc.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-const path = require("path");
-const yargs = require("yargs");
-const glob = require("glob");
-const { parseDefines } = require("./esbuild-cli");
-const { createEntryPointFile, getCommonPlugins } = require("./esbuild");
-
-const getTestSources = (exports.getTestSources = (testPattern) => {
- const {
- argv: { pattern: userPattern },
- } = yargs.options({
- pattern: {
- type: "string",
- nargs: 1,
- },
- });
- let testPaths = glob.sync(testPattern);
- const userCwd = process.env.INIT_CWD || process.cwd();
- if (userPattern !== undefined) {
- console.log(
- "Restricting test files by glob pattern: " + JSON.stringify(userPattern),
- );
- const userPaths = new Set(glob.sync(path.resolve(userCwd, userPattern)));
- testPaths = testPaths.filter((x) => userPaths.has(x));
- console.log("Loading tests from: \n" + testPaths.join("\n"));
- }
- return testPaths;
-});
-
-exports.getEntryPointConfig = (testPattern, preprocessors = []) => {
- const sources = getTestSources(testPattern);
- return {
- files: sources.map((name) => ({ pattern: name, watched: true })),
- preprocessors: Object.fromEntries(
- sources.map((name) => [name, ["esbuild", ...preprocessors]]),
- ),
- };
-};
-
-exports.getEsbuildConfig = () => {
- const { argv } = yargs.options({
- define: {
- type: "array",
- coerce: parseDefines,
- default: [],
- },
- });
- return {
- target: "es2019",
- plugins: getCommonPlugins(),
- loader: {
- ".json": "json",
- ".dat": "binary",
- ".npy": "binary",
- },
- define: argv.define,
- };
-};
diff --git a/config/karma.benchmark.js b/config/karma.benchmark.js
deleted file mode 100644
index 09cdff4ac..000000000
--- a/config/karma.benchmark.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @license
- * Copyright 2016-2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-const path = require("path");
-const {
- getEntryPointConfig,
- getEsbuildConfig,
-} = require("./karma-entry-points");
-
-module.exports = (config) => {
- config.set({
- ...getEntryPointConfig(
- path.resolve(__dirname, "..", "src", "**", "*.benchmark.ts"),
- ),
- esbuild: getEsbuildConfig(),
- frameworks: ["benchmark"],
- browsers: [
- "ChromeHeadless",
- // 'Chrome',
- // 'ChromeCanary',
- ],
- colors: true,
- browserNoActivityTimeout: 60000,
- reporters: ["benchmark"],
- // logLevel: config.LOG_DEBUG,
- singleRun: true,
- plugins: [
- "karma-benchmark",
- "karma-benchmark-reporter",
- "karma-chrome-launcher",
- "karma-firefox-launcher",
- "karma-esbuild",
- ],
- });
-};
diff --git a/config/karma.conf.js b/config/karma.conf.js
deleted file mode 100644
index 67ba14376..000000000
--- a/config/karma.conf.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google Inc.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-const path = require("path");
-const {
- getEntryPointConfig,
- getEsbuildConfig,
-} = require("./karma-entry-points");
-
-module.exports = (config) => {
- config.set({
- ...getEntryPointConfig(
- path.resolve(__dirname, "..", "src", "**", "*.spec.ts"),
- ["sourcemap"],
- ),
- esbuild: getEsbuildConfig(),
- frameworks: ["jasmine"],
- browsers: [
- // 'Firefox',
- // 'FirefoxHeadless',
- // 'ChromeHeadless_custom',
- // 'Chrome',
- // 'ChromeCanary',
- ],
- colors: true,
- browserNoActivityTimeout: 60000,
- reporters: ["mocha"],
- plugins: [
- "karma-sourcemap-loader",
- "karma-jasmine",
- "karma-mocha-reporter",
- "karma-chrome-launcher",
- "karma-firefox-launcher",
- "karma-esbuild",
- ],
- });
-};
diff --git a/config/resolve_real.js b/config/resolve_real.js
deleted file mode 100644
index a424a92d3..000000000
--- a/config/resolve_real.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google Inc.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-const path = require("path");
-const fs = require("fs");
-
-/**
- * Resolve a path to an absolute path, expanding all symbolic links. This is
- * used to ensure that the same file is not seen under multiple paths by the
- * TypeScript compiler, leading to the same file being compiled more than once,
- * which can result in various errors.
- */
-function resolveReal() {
- return fs.realpathSync(path.resolve.apply(undefined, arguments));
-}
-module.exports = resolveReal;
diff --git a/config/static-site-live-server.js b/config/static-site-live-server.js
deleted file mode 100644
index 2fb42514d..000000000
--- a/config/static-site-live-server.js
+++ /dev/null
@@ -1,359 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-// Serves a directory of static files, with programmatic control to force a
-// browser reload.
-
-// Derived from https://github.com/tapio/live-server
-// License
-// Uses MIT licensed code from Connect and Roots.
-
-// (MIT License)
-
-// Copyright (c) 2012 Tapio Vierros
-
-// 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.
-
-const fs = require("fs");
-const connect = require("connect");
-const logger = require("morgan");
-const WebSocket = require("faye-websocket");
-const path = require("path");
-const url = require("url");
-const http = require("http");
-const send = require("send");
-const es = require("event-stream");
-const os = require("os");
-require("colors");
-
-const getInjectedCode = (
- initialGeneration,
-) => `
-
-`;
-
-/**
- * Rewrite request URL and pass it back to the static handler.
- * @param staticHandler {function} Next handler
- * @param file {string} Path to the entry point file
- */
-function entryPoint(staticHandler, file) {
- if (!file)
- return (req, res, next) => {
- next();
- };
-
- return (req, res, next) => {
- req.url = "/" + file;
- staticHandler(req, res, next);
- };
-}
-
-class LiveServer {
- constructor(options = {}) {
- this.generation = "";
- this.logLevel = 2;
- this.clients = [];
- }
-
- // Based on connect.static(), but streamlined and with added code injecter
- staticServer(root) {
- let isFile = false;
- try {
- // For supporting mounting files instead of just directories
- isFile = fs.statSync(root).isFile();
- } catch (e) {
- if (e.code !== "ENOENT") throw e;
- }
- return (req, res, next) => {
- if (req.method !== "GET" && req.method !== "HEAD") return next();
- const reqpath = isFile ? "" : url.parse(req.url).pathname;
- const hasNoOrigin = !req.headers.origin;
- const injectCandidates = [
- new RegExp("
", "i"),
- new RegExp(""),
- new RegExp("", "i"),
- ];
- let injectTag = null;
- let fileExt = "";
-
- const directory = () => {
- const pathname = url.parse(req.originalUrl).pathname;
- res.statusCode = 301;
- res.setHeader("Location", pathname + "/");
- res.end("Redirecting");
- };
-
- const file = (filepath /*, stat*/) => {
- fileExt = path.extname(filepath).toLocaleLowerCase();
- const possibleExtensions = [
- "",
- ".html",
- ".htm",
- ".xhtml",
- ".php",
- ".svg",
- ];
-
- if (hasNoOrigin && possibleExtensions.indexOf(fileExt) > -1) {
- // TODO: Sync file read here is not nice, but we need to determine if the html should be
- // injected or not
- const contents = fs.readFileSync(filepath, "utf8");
- for (let i = 0; i < injectCandidates.length; ++i) {
- const match = injectCandidates[i].exec(contents);
- if (match) {
- injectTag = match[0];
- break;
- }
- }
- if (injectTag === null && this.logLevel >= 3) {
- console.warn(
- "Failed to inject refresh script!".yellow,
- "Couldn't find any of the tags ",
- injectCandidates,
- "from",
- filepath,
- );
- }
- }
- };
-
- const error = (err) => {
- if (err.status === 404) return next();
- next(err);
- };
-
- const inject = (stream) => {
- if (fileExt === ".wasm") {
- res.setHeader("Content-Type", "application/wasm");
- }
- if (injectTag) {
- const injectedCode = getInjectedCode(this.generation);
- // We need to modify the length given to browser
- const len = injectedCode.length + res.getHeader("Content-Length");
- res.setHeader("Content-Length", len);
- const originalPipe = stream.pipe;
- stream.pipe = (resp) => {
- originalPipe
- .call(
- stream,
- es.replace(
- new RegExp(injectTag, "i"),
- injectedCode + injectTag,
- ),
- )
- .pipe(resp);
- };
- }
- };
-
- send(req, reqpath, { root: root })
- .on("error", error)
- .on("directory", directory)
- .on("file", file)
- .on("stream", inject)
- .pipe(res);
- };
- }
-
- /**
- * Start a live server with parameters given as an object
- * @param host {string} Address to bind to (default: 0.0.0.0)
- * @param port {number} Port number (default: 8080)
- * @param root {string} Path to root directory (default: cwd)
- * @param mount {array} Mount directories onto a route, e.g. [['/components', './node_modules']].
- * @param logLevel {number} 0 = errors only, 1 = some, 2 = lots
- * @param file {string} Path to the entry point file
- * }].
- */
- start(options = {}) {
- const host = options.host || "0.0.0.0";
- const port = options.port !== undefined ? options.port : 8080; // 0 means random
- const root = options.root || process.cwd();
- const mount = options.mount || [];
- this.logLevel = options.logLevel === undefined ? 2 : options.logLevel;
- const file = options.file;
- const staticServerHandler = this.staticServer(root);
-
- // Setup a web server
- const app = connect();
-
- // Add logger. Level 2 logs only errors
- if (this.logLevel === 2) {
- app.use(
- logger("dev", {
- skip: (req, res) => res.statusCode < 400,
- }),
- );
- // Level 2 or above logs all requests
- } else if (this.logLevel > 2) {
- app.use(logger("dev"));
- }
- mount.forEach(function (mountRule) {
- const mountPath = path.resolve(process.cwd(), mountRule[1]);
- app.use(mountRule[0], this.staticServer(mountPath));
- if (this.logLevel >= 1)
- console.log('Mapping %s to "%s"', mountRule[0], mountPath);
- });
- app
- .use(staticServerHandler) // Custom static server
- .use(entryPoint(staticServerHandler, file));
-
- const server = http.createServer(app);
- const protocol = "http";
-
- // Handle server startup errors
- server.addListener("error", (e) => {
- if (e.code === "EADDRINUSE") {
- const serveURL = protocol + "://" + host + ":" + port;
- console.log(
- "%s is already in use. Trying another port.".yellow,
- serveURL,
- );
- setTimeout(() => {
- server.listen(0, host);
- }, 1000);
- } else {
- console.error(e.toString().red);
- this.shutdown();
- }
- });
-
- // Handle successful server
- server.addListener("listening", (/*e*/) => {
- this.server = server;
-
- const address = server.address();
- const serveHost =
- address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
- const openHost = host === "0.0.0.0" ? "127.0.0.1" : host;
-
- const serveURL = protocol + "://" + serveHost + ":" + address.port;
- const openURL = protocol + "://" + openHost + ":" + address.port;
-
- let serveURLs = [serveURL];
- if (this.logLevel > 2 && address.address === "0.0.0.0") {
- const ifaces = os.networkInterfaces();
- serveURLs = Object.keys(ifaces)
- .map((iface) => ifaces[iface])
- // flatten address data, use only IPv4
- .reduce((data, addresses) => {
- addresses
- .filter((addr) => addr.family === "IPv4")
- .forEach((addr) => {
- data.push(addr);
- });
- return data;
- }, [])
- .map((addr) => protocol + "://" + addr.address + ":" + address.port);
- }
-
- // Output
- if (this.logLevel >= 1) {
- if (serveURL === openURL)
- if (serveURLs.length === 1) {
- console.log('Serving "%s" at %s'.green, root, serveURLs[0]);
- } else {
- console.log(
- 'Serving "%s" at\n\t%s'.green,
- root,
- serveURLs.join("\n\t"),
- );
- }
- else
- console.log('Serving "%s" at %s (%s)'.green, root, openURL, serveURL);
- }
- });
-
- // Setup server to listen at port
- server.listen(port, host);
-
- // WebSocket
- server.addListener("upgrade", (request, socket, head) => {
- const ws = new WebSocket(request, socket, head);
- ws.onopen = () => {
- ws.send(this.generation);
- };
-
- ws.onclose = () => {
- this.clients = this.clients.filter((x) => x !== ws);
- };
-
- this.clients.push(ws);
- });
- this.server = server;
- return server;
- }
-
- reload() {
- this.generation = "" + Math.random();
- this.clients.forEach((ws) => {
- if (ws) ws.send(this.generation);
- });
- }
-
- shutdown() {
- if (this.server) {
- this.server.close();
- }
- }
-}
-
-module.exports = LiveServer;
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 000000000..644e58486
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,47 @@
+This directory contains examples showing how to use Neuroglancer as a library in
+a client-side web application.
+
+WARNING: Neuroglancer does not yet offer any stability guarantees for the
+JavaScript API.
+
+Neuroglancer can be used as a dependency in three ways:
+
+1. Installing the published npm package via:
+
+ ```shell
+ npm install neuroglancer
+ ```
+
+ This will use the built Neuroglancer package, with the TypeScript sources
+ already transpiled to JavaScript. This is the normal, recommended way to use
+ Neuroglancer and imposes the least requirements.
+
+2. Installing directly from the Github repository, via:
+
+ ```shell
+ npm install google/neuroglancer
+ ```
+
+3. Linking to a local checkout of the Neuroglancer repository via:
+
+ ```shell
+ npm link neuroglancer
+ ```
+
+ or
+
+ ```shell
+ npm install file:/local/path/to/neuroglancer
+ ```
+
+ This may be useful when developing Neuroglancer locally. In this case the
+ dependent project directly consumes the original TypeScript sources, and must
+ be configured with appropriate transpilation support.
+
+The following bundlers are known to be compatible:
+
+- [vite](./vite/)
+- [parcel](./parcel/)
+- [webpack](./webpack/)
+
+esbuild is not compatible due to https://github.com/evanw/esbuild/issues/795
diff --git a/examples/parcel/README.md b/examples/parcel/README.md
new file mode 100644
index 000000000..5afed86bd
--- /dev/null
+++ b/examples/parcel/README.md
@@ -0,0 +1,90 @@
+# Using Parcel to bundle a dependent project
+
+## Required configuration
+
+### Subpath imports
+
+As described in
+https://github.com/parcel-bundler/parcel/issues/7840#issuecomment-1570728149, to
+support Node.js subpath imports and exports that are used by Neuroglancer, the
+following must be included in `package.json`:
+
+```json
+{
+ "@parcel/resolver-default": {
+ "packageExports": true
+ }
+}
+```
+
+### SVG inlining
+
+Neuroglancer expects to embed SVG contents directly by importing ".svg?raw"
+modules.
+
+As this import syntax is not natively supported by Parcel, support must be added
+explicitly in `.parcelrc`:
+
+```javascript
+ transformers: {
+ "*.svg": ["...", "@parcel/transformer-inline-string"],
+ },
+```
+
+### SVGO configuration
+
+Parcel uses SVGO to minimize SVG assets. There are several issues that make the default SVGO configuration incompatible with Neuroglancer:
+
+- removeViewBox: https://github.com/svg/svgo/issues/1128
+- convertShapeToPath: https://github.com/svg/svgo/issues/1466
+
+Thw following svgo configuration, which can be specified in `svgo.config.json`,
+disables the problematic plugins:
+
+```json
+{
+ "plugins": [
+ {
+ "name": "preset-default",
+ "params": {
+ "overrides": {
+ "removeViewBox": false,
+ "convertShapeToPath": false
+ }
+ }
+ }
+ ]
+}
+```
+
+### HTML assets for auth redirect
+
+`.html` files that are used as auth redirect pages for the brainmaps and bossDB
+data sources need to have stable names. This is accomplished using the
+`parcel-namer-rewrite` plugin with the following configuration in
+`package.json`:
+
+```json
+{
+ "parcel-namer-rewrite": {
+ "rules": {
+ "(.*).html": "$1.html"
+ }
+ }
+}
+```
+
+and the following configuration in `.parcelrc`:
+
+```json
+{
+ "namers": ["parcel-namer-rewrite"]
+}
+```
+
+## Limitations
+
+- Node.js conditions [cannot be
+ configured](https://github.com/parcel-bundler/parcel/issues/9514) (without a
+ custom resolver plugin), which means that it is not possible to disable
+ specific Neuroglancer features.
diff --git a/examples/parcel/parcel-project-built/.gitignore b/examples/parcel/parcel-project-built/.gitignore
new file mode 100644
index 000000000..41889b37d
--- /dev/null
+++ b/examples/parcel/parcel-project-built/.gitignore
@@ -0,0 +1,3 @@
+/node_modules/
+/dist/
+/.parcel-cache/
diff --git a/examples/parcel/parcel-project-built/.parcelrc b/examples/parcel/parcel-project-built/.parcelrc
new file mode 100644
index 000000000..717fb3510
--- /dev/null
+++ b/examples/parcel/parcel-project-built/.parcelrc
@@ -0,0 +1,9 @@
+{
+ extends: "@parcel/config-default",
+ // Needed to ensure stable names for auth redirect .html files.
+ namers: ["parcel-namer-rewrite"],
+ // Needed for embedded svg icons.
+ transformers: {
+ "*.svg": ["...", "@parcel/transformer-inline-string"],
+ },
+}
diff --git a/examples/parcel/parcel-project-built/index.html b/examples/parcel/parcel-project-built/index.html
new file mode 100644
index 000000000..17e5273b9
--- /dev/null
+++ b/examples/parcel/parcel-project-built/index.html
@@ -0,0 +1,8 @@
+
+
+