The modern way to write TypeScript.
# Install
npm install -g @danielx/civet
# Run Civet code directly in a REPL
civet
# Transpile typed Civet code into TypeScript in a REPL
civet -c
# Compile Civet source file to TypeScript
civet < source.civet > output.ts
# Execute a .civet script
civet source.civet ...args...
# Execute a .civet source file in node
node --import @danielx/civet/register source.civet
ts, {CompilerOptions} from typescript
DefaultCompilerOptions : CompilerOptions :=
allowNonTsExtensions: true
allowJs: true
target: ts.ScriptTarget.Latest
moduleResolution: ts.ModuleResolutionKind.NodeJs
module: ts.ModuleKind.CommonJS
allowSyntheticDefaultImports: true
experimentalDecorators: true
fileCache : Record<string, any> := {}
createCompilerHost := (options: CompilerOptions, moduleSearchLocations : string[]) ->
fileExists := (fileName: string) : boolean ->
fileCache[fileName]?
readFile := (fileName: string) ->
fileCache[fileName]
Civet is essentially a tasteful superset of TypeScript.
See the documentation for examples of these and other features.
switch
can match patterns like [{type: "text", name}, ...rest]
data |> Object.keys |> console.log
equivalent to
console.log(Object.keys(data))
&
shorthand
to specify how to use left-hand side|> await
, |> yield
, and |> return
(at end)
for wrapping left-hand side with that operationx.map &.name
or x.map .name
→ x.map(a => a.name)
x.map &.profile?.name[0...3]
→ x.map(a => a.profile?.name.slice(0, 3))
x.map &.callback a, b
→ x.map($ => $.callback(a, b))
x.map !!&
→ x.map($ => !!$)
x.map &+1
→ x.map($ => $+1)
{foo()}
→ {foo: foo()}
, {props.foo}
→ {foo: props.foo}
{`${x}${y}`: z}
→ {[`${x}${y}`]: z}
data.{x,y}
or data{x,y}
→ {x: data.x, y: data.y}
{+debug, -live, !verbose}
→ {debug: true, live: false, verbose: false}
do
expressions, if
expressions, for
expressionsa := b
→ const a = b
, {a, b} := c
→ const {a, b} = c
a .= b
→ let a = b
a: number .= 5
→ let a: number = 5
(but note that a: number = 5
is the object literal {a: (number = 5)}
).@#id
→ this.#id
shorthand for private identifiersimport
shorthand: x from ./x
→ import x from "./x"
import
shorthand: import './x'
not at top level
(e.g. await import './x'
or inside a function) →
import('./x')
import {x: y} from "./z"
→ import {x as y} from "./z"
. You can still
use as
to be compatible with existing ES imports.export
shorthand: export x, y
→ export {x, y}
@( ... )
@ { ... }
<
as extends
shorthand///
Block RegExp like Python re.XInspired by solid-dsl discussions and jsx spec issues
<tag>
s or <>
s,
you can indent the children and Civet will close your tags for you>
isn't valid JSX text. For example, <For> (item) => ...
(where function body can be indented).#foo
shorthand for id="foo"
;
also #"foo bar"
, #`foo ${bar}`
, #{expr}
.foo
shorthand for class="foo"
(but must be at least one space after
tag name); also .foo.bar
, ."foo bar"
, .`foo ${bar}`
, .{expr}
"civet react"
flag uses className
instead of class
+foo
shorthand for foo={true}
, -foo
/!foo
shorthand for foo={false}
{foo}
→ foo={foo}
, {foo: bar}
→ foo={bar}
,
{...foo}
remains as is; methods and getters/setters work too....foo
shorthand for {...foo}
foo=bar
→ foo={bar}
, count=count()
→ count={count()}
,
sum=x+1
→ sum={x+1}
, list=[1, 2, 3]
→ list={[1, 2, 3]}
[expr]={value}
→ {...{[expr]: value}}
"civet solid"
flag adds correct types for JSX elements and fragments.
Use "civet solid client"
(default) for client-only code,
"civet solid server"
for server-only code (SSR only), or
"civet solid client server"
for isomorphic code that runs on
client and server (SSR + hydration).<!-- ... -->
→ {/* ... */}
.[mc]ts
→ .[mc]js
in imports (workaround for: https://github.com/microsoft/TypeScript/issues/37582):=
readonly class field initializer
class A
x := 3
class A {
readonly x = 3
}
void
return type, adding a trailing ;
or
explicit return
, or via the directive "civet -implicitReturns"
)x => ...
must become (x) => ...
The reasoning is x -> ...
=> x(function() ...)
in CoffeeScript and having ->
and =>
behave more differently than they already do is bad. Passing an anonymous function to an
application without parens is also convenient.for(i of x) ...
defaults to const declaration → for(const i of x) ...
if x, y
is not allowed. But for i = 0, l = a.length; i < l; i++, i *= 2
is allowed.case
/when
instead becomes multiple conditions.y[0..x]
). This also implies that you can't access properties
of numbers with 1..toString()
use 1.toString()
instead. When exponent follows a dot it is treated as a property access since an exponent
could be a valid property 1.e10
→ 1..e10
. The workaround is to add a trailing zero 1.0e10
or remove the dot before the exponent 1e10
.and
, or
, loop
, until
, unless
@@
instead of @
because @
is premium real estate and @id
→ this.id
, and @
is also static fields/methods, etc.
@@classDecorator
class X
@@methodDecorator
method() {}
when
inside switch automatically breaks and adds block scope.else
inside switch adds block scope.?
ex. x ? a : b
since x?
is the unary existential operator.:label
(except for special case $:
for Svelte)#!./node_modules/.bin/ts-node
console.log "hi"
Take a look at this detailed Civet // CoffeeScript comparision
Civet is not just one language; it can be configured in a variety of ways via directives to add or remove language features, or improve behavior in certain environments. See config documentation.
You have now been convinced that Civet is right for your current/next project. Here is how to set up your environment to get productive right away and have a Good Time℠.
Code coverage with c8 "just works" thanks to their source map integration and Civet's source maps.
package.json
"scripts": {
"test": "c8 mocha",
...
},
"c8": {
"extension": [
".civet"
]
},
"mocha": {
"extension": [
"civet"
],
"loader": [
"@danielx/civet/esm"
],
...
...
If you don't care for code coverage you can skip c8 (but it is so easy why not keep it?).
You can also add .js
and .ts
extensions if you want to mix and match! Even .coffee
will work if you require coffeescript/register
or add a loader for it.
Execute the tests
yarn test
Step 4: Enjoy!
Use the alpha version of Civet Language Server
The language server provides syntax highlighting, completions, hover documentation, symbols outline, red squigglies, and go to definition.
Q? Why can't I just use the built-in VSCode TypeScript LSP?
A: VSCode's built in TypeScript LSP can't resolve non .ts/.js
, not even with plugins. Maybe one day they'll allow for
plugins that let you adjust the resolver and insert a transpilation step but until then a separate language server is necessary.
Q? Sometimes the file outline disappears and the red squigglies are all in the wrong place and maybe a notification pops up about some kind of LSP error.
A: I'm sorry that happened to you but the Civet Language Server is still alpha and improving rapidly. Please let me know exactly what happened and I'll try to do better next time.
It may happen when there is a syntax error in your Civet file. You can check and see if it compiles using the CLI tool in the meantime.
Please do submit bug reports / feature requests.
I strongly recommend using esbuild for building / packaging your Civet project.
import esbuild from 'esbuild'
import civetPlugin from '@danielx/civet/esbuild-plugin'
esbuild.build({
...,
plugins: [
civetPlugin
]
}).catch(() => process.exit(1))
It's super fast and works great!
If you are so inclined, you can sponsor Civet on Open Collective.