diff --git a/example/basic/5-nested-modules/baz/src/Baz.scala b/example/basic/5-nested-modules/baz/src/Baz.scala
new file mode 100644
index 00000000000..0bb9608c48f
--- /dev/null
+++ b/example/basic/5-nested-modules/baz/src/Baz.scala
@@ -0,0 +1,17 @@
+package baz
+import scalatags.Text.all._
+import mainargs.{main, ParserForMethods, arg}
+
+object Baz {
+  @main
+  def main(@arg(name = "bar-text") barText: String,
+           @arg(name = "qux-text") quxText: String,
+           @arg(name = "baz-text") bazText: String): Unit = {
+    foo.qux.Qux.main(barText, quxText)
+
+    val value = p(bazText)
+    println("Baz.value: " + value)
+  }
+
+  def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
+}
diff --git a/example/basic/5-nested-modules/build.sc b/example/basic/5-nested-modules/build.sc
index d4817d0a213..e2b3809c664 100644
--- a/example/basic/5-nested-modules/build.sc
+++ b/example/basic/5-nested-modules/build.sc
@@ -8,16 +8,16 @@ trait MyModule extends ScalaModule{
   )
 }
 
-object wrapper extends Module{
-  object foo extends MyModule {
+object foo extends Module{
+  object bar extends MyModule
+
+  object qux extends MyModule {
     def moduleDeps = Seq(bar)
   }
-
-  object bar extends MyModule
 }
 
-object qux extends MyModule {
-  def moduleDeps = Seq(wrapper.bar, wrapper.foo)
+object baz extends MyModule {
+  def moduleDeps = Seq(foo.bar, foo.qux)
 }
 
 // Modules can be nested arbitrarily deeply within each other. The outer module
@@ -31,16 +31,17 @@ object qux extends MyModule {
 /* Example Usage
 
 > ./mill resolve __.run
-wrapper.foo.run
-wrapper.bar.run
+foo.bar.run
+foo.qux.run
 qux.run
 
-> ./mill qux.run --foo-text hello --bar-text world --qux-text today
-Foo.value: <h1>hello</h1>
-Bar.value: <p>world</p>
-Qux.value: <p>today</p>
+> ./mill baz.run --bar-text hello --qux-text world --baz-text today
+Bar.value: <h1>hello</h1>
+Qux.value: <p>world</p>
+Baz.value: <p>today</p>
 
-> ./mill wrapper.foo.run --text hello
-Foo.value: <h1>hello</h1>
+> ./mill foo.qux.run --bar-text hello --qux-text world
+Bar.value: <h1>hello</h1>
+Qux.value: <p>world</p>
 
 */
\ No newline at end of file
diff --git a/example/basic/5-nested-modules/wrapper/bar/src/Bar.scala b/example/basic/5-nested-modules/foo/bar/src/Bar.scala
similarity index 85%
rename from example/basic/5-nested-modules/wrapper/bar/src/Bar.scala
rename to example/basic/5-nested-modules/foo/bar/src/Bar.scala
index 552779c995b..a22925fca2f 100644
--- a/example/basic/5-nested-modules/wrapper/bar/src/Bar.scala
+++ b/example/basic/5-nested-modules/foo/bar/src/Bar.scala
@@ -1,11 +1,11 @@
-package bar
+package foo.bar
 import scalatags.Text.all._
 import mainargs.{main, ParserForMethods}
-object Bar {
 
+object Bar {
   @main
   def main(text: String): Unit = {
-    val value = p(text)
+    val value = h1(text)
     println("Bar.value: " + value)
   }
 
diff --git a/example/basic/5-nested-modules/qux/src/Qux.scala b/example/basic/5-nested-modules/foo/qux/src/Qux.scala
similarity index 52%
rename from example/basic/5-nested-modules/qux/src/Qux.scala
rename to example/basic/5-nested-modules/foo/qux/src/Qux.scala
index 2bd2f5b1c4d..1819642053e 100644
--- a/example/basic/5-nested-modules/qux/src/Qux.scala
+++ b/example/basic/5-nested-modules/foo/qux/src/Qux.scala
@@ -1,15 +1,12 @@
-package qux
+package foo.qux
 import scalatags.Text.all._
 import mainargs.{main, ParserForMethods, arg}
-object Qux {
-
 
+object Qux {
   @main
-  def main(@arg(name="foo-text") fooText: String,
-           @arg(name="bar-text") barText: String,
-           @arg(name="qux-text") quxText: String): Unit = {
-    foo.Foo.main(fooText)
-    bar.Bar.main(barText)
+  def main(@arg(name = "bar-text") barText: String,
+           @arg(name = "qux-text") quxText: String): Unit = {
+    foo.bar.Bar.main(barText)
 
     val value = p(quxText)
     println("Qux.value: " + value)
diff --git a/example/basic/5-nested-modules/wrapper/foo/src/Foo.scala b/example/basic/5-nested-modules/wrapper/foo/src/Foo.scala
deleted file mode 100644
index f7883549e04..00000000000
--- a/example/basic/5-nested-modules/wrapper/foo/src/Foo.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package foo
-import scalatags.Text.all._
-import mainargs.{main, ParserForMethods}
-object Foo {
-  @main
-  def main(text: String): Unit = {
-    val value = h1(text)
-    println("Foo.value: " + value)
-  }
-
-  def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
-}
diff --git a/example/web/5-webapp-scalajs-shared/build.sc b/example/web/5-webapp-scalajs-shared/build.sc
index 9b31895df20..566a57f6ce2 100644
--- a/example/web/5-webapp-scalajs-shared/build.sc
+++ b/example/web/5-webapp-scalajs-shared/build.sc
@@ -32,8 +32,7 @@ object app extends RootModule with AppScalaModule{
   }
 
   object shared extends Module{
-    trait SharedModule extends AppScalaModule{
-      def millSourcePath = super.millSourcePath / os.up
+    trait SharedModule extends AppScalaModule with PlatformScalaModule {
 
       def ivyDeps = Agg(
         ivy"com.lihaoyi::scalatags::0.12.0",
diff --git a/example/web/6-cross-platform-publishing/build.sc b/example/web/6-cross-platform-publishing/build.sc
index c0c4682cc49..81c6703bc6f 100644
--- a/example/web/6-cross-platform-publishing/build.sc
+++ b/example/web/6-cross-platform-publishing/build.sc
@@ -1,13 +1,8 @@
 import mill._, scalalib._, scalajslib._, publish._
 
-object wrapper extends Cross[WrapperModule]("2.13.10", "3.2.2")
-class WrapperModule(val crossScalaVersion: String) extends Module {
-
-  trait MyModule extends CrossScalaModule with PublishModule {
-    def artifactName = millModuleSegments.parts.dropRight(1).last
-
-    def crossScalaVersion = WrapperModule.this.crossScalaVersion
-    def millSourcePath = super.millSourcePath / os.up
+object foo extends Cross[FooModule]("2.13.10", "3.2.2")
+class FooModule(val crossScalaVersion: String) extends CrossScalaModule.Base {
+  trait Shared extends CrossScalaModule with PlatformScalaModule with PublishModule {
     def publishVersion = "0.0.1"
 
     def pomSettings = PomSettings(
@@ -25,35 +20,28 @@ class WrapperModule(val crossScalaVersion: String) extends Module {
       def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.11")
       def testFramework = "utest.runner.Framework"
     }
-
-    def sources = T.sources {
-      val platform = millModuleSegments.parts.last
-      super.sources().flatMap(source =>
-        Seq(
-          source,
-          PathRef(source.path / os.up / s"${source.path.last}-${platform}")
-        )
-      )
-    }
   }
-  trait MyScalaJSModule extends MyModule with ScalaJSModule {
+
+  trait SharedJS extends Shared with ScalaJSModule {
     def scalaJSVersion = "1.13.0"
   }
 
-  object foo extends Module{
-    object jvm extends MyModule{
+  object bar extends Module {
+    object jvm extends Shared
+
+    object js extends SharedJS
+  }
+
+  object qux extends Module{
+    object jvm extends Shared{
       def moduleDeps = Seq(bar.jvm)
       def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::upickle::3.0.0")
     }
-    object js extends MyScalaJSModule {
+    object js extends SharedJS {
       def moduleDeps = Seq(bar.js)
     }
   }
 
-  object bar extends Module{
-    object jvm extends MyModule
-    object js extends MyScalaJSModule
-  }
 }
 
 // This example demonstrates how to publish Scala modules which are both
@@ -62,43 +50,43 @@ class WrapperModule(val crossScalaVersion: String) extends Module {
 
 /* Example Usage
 
-> ./mill show wrapper[2.13.10].foo.jvm.sources # mac/linux
-wrapper/foo/src
-wrapper/foo/src-jvm
-wrapper/foo/src-2.13.10
-wrapper/foo/src-2.13.10-jvm
-wrapper/foo/src-2.13
-wrapper/foo/src-2.13-jvm
-wrapper/foo/src-2
-wrapper/foo/src-2-jvm
-
-> ./mill show wrapper[3.2.2].bar.js.sources # mac/linux
-wrapper/bar/src
-wrapper/bar/src-js
-wrapper/bar/src-3.2.2
-wrapper/bar/src-3.2.2-js
-wrapper/bar/src-3.2
-wrapper/bar/src-3.2-js
-wrapper/bar/src-3
-wrapper/bar/src-3-js
-
-> ./mill wrapper[2.13.10].foo.jvm.run
+> ./mill show foo[2.13.10].bar.jvm.sources
+foo/bar/src
+foo/bar/src-jvm
+foo/bar/src-2.13.10
+foo/bar/src-2.13.10-jvm
+foo/bar/src-2.13
+foo/bar/src-2.13-jvm
+foo/bar/src-2
+foo/bar/src-2-jvm
+
+> ./mill show foo[3.2.2].qux.js.sources
+foo/qux/src
+foo/qux/src-js
+foo/qux/src-3.2.2
+foo/qux/src-3.2.2-js
+foo/qux/src-3.2
+foo/qux/src-3.2-js
+foo/qux/src-3
+foo/qux/src-3-js
+
+> ./mill foo[2.13.10].qux.jvm.run
 Bar.value: <p>world Specific code for Scala 2.x</p>
 Parsing JSON with ujson.read
-Foo.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
+Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
 
-> ./mill wrapper[3.2.2].foo.js.run
+> ./mill foo[3.2.2].qux.js.run
 Bar.value: <p>world Specific code for Scala 3.x</p>
 Parsing JSON with js.JSON.parse
-Foo.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
+Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
 
 > ./mill __.publishLocal
-Publishing Artifact(com.lihaoyi,bar_sjs1_2.13,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,bar_2.13,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,foo_sjs1_2.13,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,foo_2.13,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,bar_sjs1_3,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,bar_3,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,foo_sjs1_3,0.0.1) to ivy repo
-Publishing Artifact(com.lihaoyi,foo_3,0.0.1) to ivy repo
-*/
\ No newline at end of file
+Publishing Artifact(com.lihaoyi,foo-bar_sjs1_2.13,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-bar_2.13,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-qux_sjs1_2.13,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-qux_2.13,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-bar_sjs1_3,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-bar_3,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-qux_sjs1_3,0.0.1) to ivy repo
+Publishing Artifact(com.lihaoyi,foo-qux_3,0.0.1) to ivy repo
+*/
diff --git a/example/web/6-cross-platform-publishing/wrapper/bar/src-2/BarVersionSpecific.scala b/example/web/6-cross-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/wrapper/bar/src-2/BarVersionSpecific.scala
rename to example/web/6-cross-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/wrapper/bar/src-3/BarVersionSpecific.scala b/example/web/6-cross-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/wrapper/bar/src-3/BarVersionSpecific.scala
rename to example/web/6-cross-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/wrapper/bar/src/Bar.scala b/example/web/6-cross-platform-publishing/foo/bar/src/Bar.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/wrapper/bar/src/Bar.scala
rename to example/web/6-cross-platform-publishing/foo/bar/src/Bar.scala
diff --git a/example/web/6-cross-platform-publishing/wrapper/bar/test/src/BarTests.scala b/example/web/6-cross-platform-publishing/foo/bar/test/src/BarTests.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/wrapper/bar/test/src/BarTests.scala
rename to example/web/6-cross-platform-publishing/foo/bar/test/src/BarTests.scala
diff --git a/example/web/6-cross-platform-publishing/wrapper/foo/src-js/FooPlatformSpecific.scala b/example/web/6-cross-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
similarity index 82%
rename from example/web/6-cross-platform-publishing/wrapper/foo/src-js/FooPlatformSpecific.scala
rename to example/web/6-cross-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
index 8a31c3529da..d9f57981da4 100644
--- a/example/web/6-cross-platform-publishing/wrapper/foo/src-js/FooPlatformSpecific.scala
+++ b/example/web/6-cross-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
@@ -1,6 +1,6 @@
-package foo
+package qux
 import scala.scalajs.js
-object FooPlatformSpecific {
+object QuxPlatformSpecific {
   def parseJsonGetKeys(s: String): Set[String] = {
     println("Parsing JSON with js.JSON.parse")
     js.JSON.parse(s).asInstanceOf[js.Dictionary[_]].keys.toSet
diff --git a/example/web/6-cross-platform-publishing/wrapper/foo/src-jvm/FooPlatformSpecific.scala b/example/web/6-cross-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
similarity index 76%
rename from example/web/6-cross-platform-publishing/wrapper/foo/src-jvm/FooPlatformSpecific.scala
rename to example/web/6-cross-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
index 6d92e2e15f4..8feff343246 100644
--- a/example/web/6-cross-platform-publishing/wrapper/foo/src-jvm/FooPlatformSpecific.scala
+++ b/example/web/6-cross-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
@@ -1,5 +1,5 @@
-package foo
-object FooPlatformSpecific {
+package qux
+object QuxPlatformSpecific {
   def parseJsonGetKeys(s: String): Set[String] = {
     println("Parsing JSON with ujson.read")
     ujson.read(s).obj.keys.toSet
diff --git a/example/web/6-cross-platform-publishing/wrapper/foo/src/Foo.scala b/example/web/6-cross-platform-publishing/foo/qux/src/Qux.scala
similarity index 69%
rename from example/web/6-cross-platform-publishing/wrapper/foo/src/Foo.scala
rename to example/web/6-cross-platform-publishing/foo/qux/src/Qux.scala
index aa72b78de72..77bad3e03af 100644
--- a/example/web/6-cross-platform-publishing/wrapper/foo/src/Foo.scala
+++ b/example/web/6-cross-platform-publishing/foo/qux/src/Qux.scala
@@ -1,9 +1,9 @@
-package foo
+package qux
 import scalatags.Text.all._
-object Foo {
+object Qux {
   def main(args: Array[String]): Unit = {
     println("Bar.value: " + bar.Bar.value)
     val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
-    println("Foo.main: " + FooPlatformSpecific.parseJsonGetKeys(string).map(p(_)))
+    println("Qux.main: " + QuxPlatformSpecific.parseJsonGetKeys(string).map(p(_)))
   }
 }
diff --git a/example/web/6-cross-platform-publishing/wrapper/foo/test/src/FooTests.scala b/example/web/6-cross-platform-publishing/foo/qux/test/src/QuxTests.scala
similarity index 64%
rename from example/web/6-cross-platform-publishing/wrapper/foo/test/src/FooTests.scala
rename to example/web/6-cross-platform-publishing/foo/qux/test/src/QuxTests.scala
index 7ca5f69ef3d..bdcf6fa9203 100644
--- a/example/web/6-cross-platform-publishing/wrapper/foo/test/src/FooTests.scala
+++ b/example/web/6-cross-platform-publishing/foo/qux/test/src/QuxTests.scala
@@ -1,10 +1,10 @@
-package foo
+package qux
 import utest._
-object FooTests extends TestSuite {
+object QuxTests extends TestSuite {
   def tests = Tests {
     test("parseJsonGetKeys") {
       val string = """{"i": "am", "cow": "hear", "me": "moo}"""
-      val keys = FooPlatformSpecific.parseJsonGetKeys(string)
+      val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
       assert(keys == Set("i", "cow", "me"))
       keys
     }
diff --git a/scalalib/src/mill/scalalib/CrossModuleBase.scala b/scalalib/src/mill/scalalib/CrossModuleBase.scala
index 6cae4fb596d..f608f563aec 100644
--- a/scalalib/src/mill/scalalib/CrossModuleBase.scala
+++ b/scalalib/src/mill/scalalib/CrossModuleBase.scala
@@ -12,7 +12,8 @@ trait CrossModuleBase extends ScalaModule {
   protected def scalaVersionDirectoryNames: Seq[String] =
     ZincWorkerUtil.matchingVersions(crossScalaVersion)
 
-  override def artifactName: T[String] = millModuleSegments.parts.init.mkString("-")
+  protected def wrapperSegments = millModuleSegments.parts
+  override def artifactNameParts = super.artifactNameParts().patch(wrapperSegments.size - 1, Nil, 1)
   implicit def crossSbtModuleResolver: Resolver[CrossModuleBase] =
     new Resolver[CrossModuleBase] {
       def resolve[V <: CrossModuleBase](c: Cross[V]): V = {
diff --git a/scalalib/src/mill/scalalib/CrossScalaModule.scala b/scalalib/src/mill/scalalib/CrossScalaModule.scala
index 8edf01d8f87..330331477c1 100644
--- a/scalalib/src/mill/scalalib/CrossScalaModule.scala
+++ b/scalalib/src/mill/scalalib/CrossScalaModule.scala
@@ -4,9 +4,10 @@ import mill.api.PathRef
 import mill.T
 
 /**
- * A [[ScalaModule]] with is suited to be used with [[mill.define.Cross]].
- * It supports additional source directories with the scala version pattern as suffix (`src-{scalaversionprefix}`),
- * e.g.
+ * A [[ScalaModule]] which is suited to be used with [[mill.define.Cross]].
+ * It supports additional source directories with the scala version pattern
+ * as suffix (`src-{scalaversionprefix}`), e.g.
+ *
  * - src
  * - src-2.11
  * - src-2.12.3
@@ -25,3 +26,20 @@ trait CrossScalaModule extends ScalaModule with CrossModuleBase { outer =>
   }
   trait Tests extends CrossScalaModuleTests
 }
+
+object CrossScalaModule {
+
+  /**
+   * Used with a [[mill.define.Cross]] when you want [[CrossScalaModule]]'s
+   * nested within it
+   */
+  trait Base extends mill.Module {
+    def crossScalaVersion: String
+    private def wrapperSegments0 = millModuleSegments.parts
+    trait CrossScalaModule extends mill.scalalib.CrossScalaModule {
+      override def wrapperSegments = wrapperSegments0
+      def crossScalaVersion = Base.this.crossScalaVersion
+
+    }
+  }
+}
diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala
index 9e21617b991..c783134f510 100644
--- a/scalalib/src/mill/scalalib/JavaModule.scala
+++ b/scalalib/src/mill/scalalib/JavaModule.scala
@@ -858,7 +858,9 @@ trait JavaModule
    * For example, by default a scala module foo.baz might be published as foo-baz_2.12 and a java module would be foo-baz.
    * Setting this to baz would result in a scala artifact baz_2.12 or a java artifact baz.
    */
-  def artifactName: T[String] = millModuleSegments.parts.mkString("-")
+  def artifactName: T[String] = artifactNameParts().mkString("-")
+
+  def artifactNameParts: T[Seq[String]] = millModuleSegments.parts
 
   /**
    * The exact id of the artifact to be published. You probably don't want to override this.
diff --git a/scalalib/src/mill/scalalib/PlatformScalaModule.scala b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
new file mode 100644
index 00000000000..21f42e1e26f
--- /dev/null
+++ b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
@@ -0,0 +1,29 @@
+package mill.scalalib
+import mill._
+
+/**
+ * A [[ScalaModule]] intended for defining `.jvm`/`.js`/`.native` submodules
+ * It supports additional source directories per platform, e.g. `src-jvm/` or
+ * `src-js/` and can be used inside a [[CrossScalaModule.Base]], to get one
+ * source folder per platform per version e.g. `src-2.12-jvm/`.
+ *
+ * Adjusts the [[millSourcePath]] and [[artifactNameParts]] to ignore the last
+ * path segment, which is assumed to be the name of the platform the module is
+ * built against and not something that should affect the filesystem path or
+ * artifact name
+ */
+trait PlatformScalaModule extends ScalaModule {
+  override def millSourcePath = super.millSourcePath / os.up
+
+  override def sources = T.sources {
+    val platform = millModuleSegments.parts.last
+    super.sources().flatMap(source =>
+      Seq(
+        source,
+        PathRef(source.path / os.up / s"${source.path.last}-${platform}")
+      )
+    )
+  }
+
+  override def artifactNameParts = super.artifactNameParts().dropRight(1)
+}