diff --git a/README.md b/README.md
index 4c4db1f..ff56b09 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,323 @@
# sbt-vite
-An SBT plugin to run and test Scala.js projects using vite
+An SBT plugin to build and test Scala.js projects using vite.
## Usage
-This plugin requires sbt 1.0.0+
+This plugin requires sbt 1.0.0+.
+
+To use sbt-vite in your project, add the following line to `projects/plugins.sbt`:
+
+```sbt
+addSbtPlugin("io.github.johnhungerford.sbt.vite" % "sbt-vite" % "0.0.7")
+```
+
+In `build.sbt`, include `SbtVitePlugin` in `.enablePlugins(...)` in any Scala.js project
+that needs to be bundled with JavaScript dependencies, and configure the Scala.js plugin
+to use ECMAScript modules:
+
+```sbt
+scalaJSLinkerConfig ~= {
+ _.withModuleKind(ModuleKind.ESModule)
+}
+```
+
+### Building
+
+Add the following files to the root directory of your project or sub-project:
+
+`index.html`
+```html
+
+
+
+
+
+ sbt-vite
+
+
+
+
+
+```
+
+If your Scala.js project is runnable (i.e., if it has a `def main` entrypoint and
+`scalaJSUseMainModuleInitializer := true` in build.sbt), you can simply import the
+application to be run as follows
+
+`main.js`:
+```javascript
+// 'scalajs:main.js' will be resolved by vite to the output of the Scala.js linker
+import 'scalajs:main.js'
+```
+
+Otherwise you can simply import any exported objects from your Scala.js project as
+follows:
+
+`main.js`:
+```javascript
+import { someLib, otherLib } from 'scalajs:main.js';
+
+...
+
+someLib.doSomething();
+otherLib.doSomethingElse();
+
+...
+```
+
+Once you have your html and js entrypoints in place, you can run the following to
+generate a web bundle:
+
+```shell
+sbt viteBuild
+```
+
+This will compile your project, generate an appropriate vite configuration, and run
+vite on all artifacts. By default, the bundle will persisted at
+`[project-directory]/target/scala-[x.x.x]/sbt-vite/bundle`
### Testing
-Run `test` for regular unit tests.
+Run tests using the usual command:
+
+```shell
+sbt test
+```
+
+This will use vite to bundle the linked JavaScript test executable with any dependencies
+prior to running it.
+
+## Dependency management
+
+This plugin would not be of much use if it did not resolve dependencies properly. There
+are currently three modes of dependency management.
+
+### Fully managed
+
+By default, sbt-vite will manage all dependencies. To explicitly enable this
+mode, add the following setting to `build.sbt`:
+
+```sbt
+viteDependencyManagement := DependencyManagement.Managed(NpmManager.Npm)
+```
+
+You can also pass `NpmManager.Yarn` or `NpmManager.Pnpm` to use `yarn` or `pnpm` to
+install npm dependencies instead of `npm` (default).
+
+You can declare npm dependencies to be used in your project using the following two
+sbt settings:
+
+```sbt
+npmDependencies ++= Seq(
+ "react" -> "^18.2.0",
+ "react-dom" -> "^18.2.0",
+)
+
+npmDevDependencies ++= Seq(
+ "vite-plugin-eslint" -> "^1.8.1",
+)
+```
+
+For `npmDevDependencies` in particular, be sure to use `++=` instead of `:=`, as
+sbt-vite includes several dependencies required to execute the build.
+
+#### Other non-Scala.js sources
+
+In addition to bundling npm modules, sbt-vite will bundle Scala.js outputs with
+imported sources, such as `JavaScript`, `TypeScript`, `css`, `less`, and others.
+
+Source files and directories can be declared for inclusion using the `viteOtherSources`
+setting:
+
+```sbt
+viteOtherSources := Seq(
+ Location.FromProjectRoot(file("src/main/typescript")),
+ Location.FromProjectRoot(file("src/main/styles")),
+ Location.FromRoot(file("common/typescript")),
+ Location.FromRoot(file("common/styles")),
+ Location.FromRoot(file("common/index.html")),
+ Location.FromRoot(file("common/main.js")),
+)
+```
+
+(See [appendix below](#location))
+
+For any declared source that points to a directory, sbt-vite will copy all the files
+and directories within it to the build directory prior to running `vite`. Any declared
+source that is a file will be copied directly to the build directory.
+
+Accordingly, any sources declared in your build can be imported as expected:
+
+```scala
+// This will import either from [project]/src/main/typescript/someDir/someTypeScriptModule.ts
+// or from common/typescript/someDir/someTypeScriptModule.ts
+@js.native
+@JSImport("/someDir/someTypeScriptModule", JSImport.Default)
+object TypeScriptImport extends js.Object
+
+// This will import either from [project]/src/main/styles/someStyle.css or from
+// common/styles/someStyle.css
+@js.native
+@JSImport("/someStyle.css?inline", JSImport.Namespace)
+object CssImport extends js.Object
+```
+
+These imports will work correctly in JS and TS sources as well:
+
+```javascript
+import someModule from '/someDir/someTypeScriptModule';
+import '/someStyle.css';
+```
+
+### Manual
+
+When dependency management is set to `Manual`, you will be responsible for managing any
+dependencies needed for vite to bundle your project. sbt-vite will neither install any
+npm packages nor copy over any source directories. Instead, sbt-vite will simply run from
+`viteProjectRoot` (by default set to the root directory of your project or sub-project,
+see [appendix below](#location)), from which it will expect to be able to resolve any
+imports, whether via local sources or `node_modules`.
+
+Note that when using manual mode, `viteBuild` and `test` will fail unless you install
+`vite`, `lodash`, `rollup-plugin-sourcemaps`, and `vite-plugin-scalajs` as dev
+dependencies. To have sbt-vite install these for you, use `InstallOnly` dependency
+management (see [below](#install-only)).
+
+To enable manual dependency management, using the following setting in `build.sbt`:
+
+```sbt
+viteDependencyManagement := DependencyManagement.Manual
+```
+
+### Install-only
+
+Install-only dependency management is like manual mode, only it will automatically
+install any dependencies declared in `npmDependencies` and `npmDevDependencies` in
+your `viteProjectRoot` directory. This will ensure that any requirements needed simply
+to run the vite build commands will be in place, as these are included by default in
+`npmDevDependencies`.
+
+## Customization
+
+To allow users to customize the build process sbt-vite provides various settings for
+overriding configurations.
+
+### Build command customization
+
+Every build command executed by sbt-vite can be passed custom arguments or options as
+well as custom environment variables using the following settings:
+
+```sbt
+// Pass additional options or arguments to the "vite build" command
+viteExtraArgs += "--mode=development"
+
+// Set environment variables for the "vite build" command
+viteEnvironment += "NODE_ENV" -> "development"
+
+// Pass additional options or arguments to the "npm install" command
+npmExtrArgs += "--legacy-peer-deps"
+
+// Set environment variables for the "npm install" command
+npmEnvironment += "NPM_CONFIG_PREFIX" -> "global_node_modules"
+
+// Pass additional options or arguments to the "pnpm add" command
+pnpmExtrArgs += "--save-exact"
+
+// Set environment variables for the "pnpm add" command
+pnpmEnvironment += "NPM_CONFIG_PREFIX" -> "global_node_modules"
+
+// Pass additional options or arguments to the "yarn add" command
+yarnExtrArgs += "--audit"
+
+// Set environment variables for the "yarn add" command
+yarnEnvironment += "NPM_CONFIG_PREFIX" -> "global_node_modules"
+```
+
+### Vite config overrides
+
+sbt-vite generates configuration scripts with reasonable defaults for full builds
+(i.e., `viteBuild`, which is an alias for `Compile / viteBuild`), and test builds
+(i.e., `Test / viteBuild`, which prepares a bundle to be executed by `Test / test`).
+
+To override these defaults, you can use the setting `viteConfigSources` to provide
+one or more configuration scripts that will be merged with the defaults, allowing
+you to override various settings. Note that the following configuration properties
+cannot be overridden:
+1. `root`
+2. `build.rollupOptions.input`, and
+3. `build.rollupOptions.output.dir`
+
+`viteConfigSources` must specify valid javascript files that provide a default export
+of one of the following two forms:
+1. a simple [vite configuration object](https://github.com/vitejs/vite/blob/997a6951450640fed8cf19e58dce0d7a01b92392/packages/vite/src/node/config.ts#L127)
+2. a function that consumes a [vite environment configuration](https://github.com/vitejs/vite/blob/997a6951450640fed8cf19e58dce0d7a01b92392/packages/vite/src/node/config.ts#L78)
+ and returns a [vite configuration](https://github.com/vitejs/vite/blob/997a6951450640fed8cf19e58dce0d7a01b92392/packages/vite/src/node/config.ts#L127).
+
+Note that neither of these should be wrapped in `defineConfig`, as this will be called
+after merging imported overrides.
+
+Note also that the `viteConfigSources` will be merged in order, so later sources in the
+`Seq` will have precedence over prior sources.
+
+`viteConfigSources` can be scoped to `Compile` and `Test` to provide different
+customizations for your full build and for tests.
+
+#### Example
+
+The following vite config source provides overrides to support JSX (React),
+bundle source maps (disabled by default except in tests), and break out several
+library dependencies into separate chunks:
+
+`build.sbt`:
+```sbt
+Compile / viteConfigSources += Location.FromRoot(file("vite.config-build.js"))
+```
+
+'vite.config-build.js':
+```javascript
+import react from '@vitejs/plugin-react';
+
+import sourcemaps from 'rollup-plugin-sourcemaps';
-Run `scripted` for [sbt script tests](http://www.scala-sbt.org/1.x/docs/Testing-sbt-plugins.html).
+export default (env)=> ({
+ // Array properties will concat on merge, so this will be added
+ // to plugins, instead of overwriting
+ plugins: [
+ react(),
+ ],
+ build: {
+ sourcemap: true,
+ rollupOptions: {
+ plugins: [sourcemaps()],
+ output: {
+ strict: false,
+ chunkFileNames: '[name]-[hash:10].js',
+ manualChunks: {
+ lodash: ['lodash'],
+ react: ['react'],
+ 'react-dom': ['react-dom'],
+ 'react-router-dom': ['react-router-dom'],
+ }
+ }
+ },
+ },
+});
+```
-### CI
+## Appendices
-The generated project uses [sbt-github-actions](https://github.com/djspiewak/sbt-github-actions) as a plugin to generate workflows for GitHub actions. For full details of how to use it [read this](https://github.com/djspiewak/sbt-github-actions/blob/main/README.md)
+### Location
-### Publishing
+In order to identify directories and files more easily, sbt-vite uses a custom type
+`Location`, which allows you to specify paths relative to commonly used base directories:
-1. publish your source to GitHub
-2. Follow the instructions in [sbt-ci-release](https://github.com/olafurpg/sbt-ci-release/blob/main/readme.md) to create a sonatype account and setup your keys
-3. `sbt ci-release`
-4. [Add your plugin to the community plugins list](https://github.com/sbt/website#attention-plugin-authors)
-5. [Claim your project an Scaladex](https://github.com/scalacenter/scaladex-contrib#claim-your-project)
+1. `Location.Root`: the base directory of the root project
+2. `Location.ProjectRoot`: the base directory of the currently scoped project
+3. `Location.FromRoot(file)`: provide a `File` relative to `Location.Root`. Note
+ that `file` must have a relative path.
+4. `Location.FromProject(file)`: provide a `File` relative to `Location.ProjectRoot`.
+ `file` must have a relative path.
+5. `Location.FromCwd(file)`: provide a `File` relative to the current working directory
+ (which will presumably always be the same as `Location.Root`). `file` need not
+ be relative, so use this to specify a location using an absolute path.
diff --git a/src/main/scala/sbtvite/sbtplugin/SbtVitePlugin.scala b/src/main/scala/sbtvite/sbtplugin/SbtVitePlugin.scala
index 60743a8..4a662ba 100644
--- a/src/main/scala/sbtvite/sbtplugin/SbtVitePlugin.scala
+++ b/src/main/scala/sbtvite/sbtplugin/SbtVitePlugin.scala
@@ -322,7 +322,8 @@ object SbtVitePlugin extends AutoPlugin {
private val testSettings = Seq(
viteUseExistingConfig := false,
- viteTargetDirectory := target.value / "sbt-vite-test",
+ viteTargetDirectory :=
+ target.value / s"scala-${scalaVersion.value}" / "sbt-vite-test",
viteExecutionDirectory := {
(Test / viteDependencyManagement).value match {
@@ -558,7 +559,8 @@ object SbtVitePlugin extends AutoPlugin {
private val compileSettings = Seq(
viteUseExistingConfig := viteUseExistingConfig.value,
- viteTargetDirectory := target.value / "sbt-vite",
+ viteTargetDirectory :=
+ target.value / s"scala-${scalaVersion.value}" / "sbt-vite",
viteExecutionDirectory := {
(Compile / viteDependencyManagement).value match {