Skip to content

Instantly share code, notes, and snippets.

@davegurnell
Last active December 14, 2015 22:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davegurnell/5162070 to your computer and use it in GitHub Desktop.
Save davegurnell/5162070 to your computer and use it in GitHub Desktop.
Rough idea for an update to the Untyped sbt-js and sbt-less plugins: create a single asset compiler plugin that allows flexible creation of compiler functions.
// TODO: This code doesn't distinguish between "main" files for which we want to generate outputs,
// and "library" files that are used in compilation but don't generate outputs.
case class SourceLocation(val rel: File, val relTo: File, val abs: File)
// Compilation may take several steps (Coffeescript => CommonJS => Concat => Uglifyjs etc).
// Sources are cached on disk after each step, allowing us to check timestamps to avoid
// unnecessary recompilation. Compiler steps accept one or more Sources as an argument and
// emit one or more Sources as outputs. Each Source knows where the original source file is
// located, where the target file is going to end up, and where the content is cached at the
// moment.
case class Source(
val name: String, // The name used to require this file from another
val src: SourceLocation, // Where did this file originally come from ?
val des: SourceLocation, // Where is this file going to end up ?
val loc: SourceLocation, // Where is the current cached version of this file
val deps: List[SourceLocation] // Ordering dependencies. Calculated by a resolver and used in a CompileManyOne
)
// Basic types:
type SearchPath = List[File] // A "classpath" for resolving dependencies, similar to current sbt-{js,less} functionality
type Sources = List[Source] // A set of sources output by a compilation step
// Resolvers find sources of a given type given a search path.
// They extract the name and dependencies from the file and produce a Source object.
type SourceResolver = SearchPath => Sources
// Resolvers for different source types:
// Arguments are things like path finders and include and exclude filters.
case class ResolveCoffee(...) extends SourceResolver
case class ResolveJs(...) extends SourceResolver
// The grand goal of a compiler is to turn a search path into a set of sources:
type Compiler = SearchPath => Sources
// Generic compilation steps:
type CompileOneOne = Source => Source
type CompileManyOne = List[Source] => Source
type CompileManyMany = List[Source] => List[Source]
// Turn a one-one compilation step into a many-many (should probably be using Scalaz):
implicit def liftOneOne(fn: CompileOneOne): CompileManyMany = {
(in: Sources) =>
in map fn
}
def compileAll(fns: Compiler *): Compiler = {
(in: SearchPath) =>
fns.toList.flatMap(_ apply in)
}
// One-to-one compilation steps:
case class Coffee(val bare: Boolean) extends CompileOneOne { ... }
case object CommonJs extends CompileOneOne { ... }
case object UglifyJs extends CompileOneOne { ... }
// Many-to-one compilation steps:
object Concat extends CompileManyOne { ... }
object GoogleClosure(val options: CompilerOptions) extends CompileManyOne { ... }
// These steps take a search path, resolve all relevant files, and compile them into temporary JS files:
val coffeeCompiler: Compiler = ResolveCoffee(...) andThen Coffee(false) andThen CommonJs
val jsCompiler: Compiler = ResolveJs(...) andThen CommonJs
// The whole compiler:
val compile = compileAll(coffeeCompiler, jsCompiler) andThen Concat andThen UglifyJs
val outputs = compile(searchPath)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment