├── test ├── samples │ ├── simple_class.jadelet │ ├── at_attributes.jadelet │ ├── nesting.jadelet │ ├── empty_lines.jadelet │ ├── attributes_with_nesting.jadelet │ ├── single_quotes.jadelet │ ├── attributes.jadelet │ └── multiple.jadelet ├── register.mjs ├── ast.civet ├── register.civet ├── runtime.civet ├── fixtures │ └── esbuild.js ├── api.civet ├── svg.civet ├── primitives.civet ├── helper.civet ├── indentation.civet ├── text.civet ├── compiler.civet ├── input.civet ├── random_tags.civet ├── elements.civet ├── custom.civet ├── computed.civet ├── retain.civet ├── attributes.civet ├── memory-usage.civet ├── checkbox.civet ├── multiple.civet ├── classes.civet ├── esbuild.civet ├── ids.civet ├── styles.civet ├── events.civet ├── subrender.civet ├── efficiency.civet └── select.civet ├── esbuild-plugin.d.ts ├── .vscode └── extensions.json ├── .gitignore ├── source ├── parser.hera.d.ts ├── esbuild.civet ├── cli.civet ├── parser.hera └── index.civet ├── register.js ├── script └── compile ├── TODO.md ├── types ├── ambient.d.ts ├── main.d.ts └── types.d.ts ├── .github └── workflows │ └── build.yml ├── esbuild-plugin.js ├── loader.mjs ├── tsconfig.json ├── LICENSE ├── CHANGELOG.md ├── package.json ├── NOTES.md ├── README.md └── yarn.lock /test/samples/simple_class.jadelet: -------------------------------------------------------------------------------- 1 | .duder(class=@classes) 2 | -------------------------------------------------------------------------------- /test/samples/at_attributes.jadelet: -------------------------------------------------------------------------------- 1 | button(@click class=@classes @name) 2 | -------------------------------------------------------------------------------- /test/samples/nesting.jadelet: -------------------------------------------------------------------------------- 1 | div 2 | h2 @name 3 | input(value=@first) 4 | input(value=@last) 5 | -------------------------------------------------------------------------------- /test/samples/empty_lines.jadelet: -------------------------------------------------------------------------------- 1 | li 2 | 3 | ul 4 | 5 | Yo 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /esbuild-plugin.d.ts: -------------------------------------------------------------------------------- 1 | declare var _exports: (options?: {}) => import("esbuild").Plugin 2 | export = _exports; 3 | -------------------------------------------------------------------------------- /test/samples/attributes_with_nesting.jadelet: -------------------------------------------------------------------------------- 1 | .duder(name="boss" id="hot") 2 | .nested(class="wat") 3 | input 4 | -------------------------------------------------------------------------------- /test/samples/single_quotes.jadelet: -------------------------------------------------------------------------------- 1 | img(src='http://duderman.info/#{yolocountyusa}' data-rad='what the duder?') 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "phil294.coffeesense", 4 | "DanielX.hera" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/samples/attributes.jadelet: -------------------------------------------------------------------------------- 1 | .yolo(id=@id class="cool cat" data-test="test" dude=@test) 2 | #test.yolo2(class=@duder) 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .nyc_output/ 3 | bin/ 4 | coverage/ 5 | dist/ 6 | gh-pages/ 7 | node_modules/ 8 | test/samples/*.js 9 | test/fixtures/out/ 10 | -------------------------------------------------------------------------------- /test/register.mjs: -------------------------------------------------------------------------------- 1 | import { register } from "node:module"; 2 | import { pathToFileURL } from "node:url"; 3 | register("@danielx/civet/esm", pathToFileURL("./")); 4 | -------------------------------------------------------------------------------- /source/parser.hera.d.ts: -------------------------------------------------------------------------------- 1 | import { JadeletASTNode } from "../types/types" 2 | 3 | declare const JadeletParser: { 4 | parse(source: string): JadeletASTNode; 5 | } 6 | 7 | export = JadeletParser 8 | -------------------------------------------------------------------------------- /test/samples/multiple.jadelet: -------------------------------------------------------------------------------- 1 | div 2 | input(type="text" @value) 3 | select(@value @options) 4 | hr 5 | input(type="range" value=@value min="1" @max) 6 | hr 7 | progress(@value @max) 8 | -------------------------------------------------------------------------------- /test/ast.civet: -------------------------------------------------------------------------------- 1 | describe "ast", -> 2 | it "should fail on a corrupted ast", -> 3 | assert.throws -> 4 | ast := ["h1", {}, [5]] 5 | T := Jadelet.exec(ast as any) 6 | T() 7 | , /oof/ 8 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | var parse, exec, fs; 2 | ({ parse, exec } = require("./")); 3 | fs = require("fs"); 4 | 5 | require.extensions[".jadelet"] = function (module, filename) { 6 | return module.exports = exec(parse(fs.readFileSync(filename, 'utf8'))); 7 | }; 8 | -------------------------------------------------------------------------------- /test/register.civet: -------------------------------------------------------------------------------- 1 | describe "register", -> 2 | it "should register", -> 3 | await import "../register.js" 4 | 5 | // @ts-ignore 6 | { createRequire } from 'node:module' 7 | const require = createRequire(import.meta.url); 8 | 9 | T := require "./samples/simple_class" 10 | assert T() 11 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | rm -r dist/ || true 5 | mkdir --parents dist 6 | 7 | # normal files 8 | coffee build/main.coffee 9 | 10 | # cli 11 | mkdir -p bin 12 | BIN="dist/jadelet" 13 | echo "#!/usr/bin/env node" | cat - dist/cli.js > "$BIN" 14 | chmod +x "$BIN" 15 | rm dist/cli.js 16 | -------------------------------------------------------------------------------- /test/runtime.civet: -------------------------------------------------------------------------------- 1 | describe "Jadelet Runtime", -> 2 | it "should provide Observable", -> 3 | assert Jadelet.Observable 4 | 5 | it "should throw an error on multiple root elements", -> 6 | assert.throws -> 7 | makeTemplate """ 8 | h1 yo 9 | p what's my information architecture lol 10 | """ 11 | -------------------------------------------------------------------------------- /test/fixtures/esbuild.js: -------------------------------------------------------------------------------- 1 | // Test source file for esbuild plugin build/plugin-test.coffee 2 | 3 | module.exports = { 4 | Simple: require("../samples/simple_class.jadelet"), 5 | Multiple: require("../samples/multiple.jadelet"), 6 | Attributes: require("../samples/attributes.jadelet"), 7 | Nesting: require("../samples/nesting.jadelet"), 8 | } 9 | -------------------------------------------------------------------------------- /test/api.civet: -------------------------------------------------------------------------------- 1 | import "./helper.civet" 2 | 3 | describe "Jadelet API", -> 4 | it "should parse and exec", -> 5 | {parse, exec} := Jadelet 6 | 7 | t := exec parse """ 8 | h1 hi 9 | """ 10 | 11 | assert.equal t().textContent, "hi" 12 | 13 | it "should compile", -> 14 | {compile} := Jadelet 15 | 16 | s := compile """ 17 | h1 hi 18 | """ 19 | 20 | assert s 21 | -------------------------------------------------------------------------------- /test/svg.civet: -------------------------------------------------------------------------------- 1 | describe "svg", -> 2 | it "should render svg", -> 3 | src := """ 4 | section 5 | h2 svg test 6 | svg(width=100 height=100) 7 | circle(cx=80 cy=80 r=30 fill="red") 8 | p awesome 9 | """ 10 | 11 | template := makeTemplate src 12 | element := template() 13 | 14 | assert.equal element.querySelector('svg')?.namespaceURI, "http://www.w3.org/2000/svg" 15 | -------------------------------------------------------------------------------- /test/primitives.civet: -------------------------------------------------------------------------------- 1 | describe "Primitives", -> 2 | template := makeTemplate """ 3 | div 4 | @string 5 | @boolean 6 | @number 7 | @array 8 | """ 9 | 10 | it "should render correctly", -> 11 | model := 12 | string: "hey" 13 | boolean: true 14 | number: 5 15 | array: [1, true, "e"] 16 | 17 | element := template(model) 18 | assert.equal element.textContent, "heytrue51truee" 19 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | - [ ] Explore Custom elements 5 | - [ ] Optionally pass `document` when rendering? 6 | - [x] Explore adding TypeScript typings 7 | - [x] declare "jadelet/esbuild-plugin" module 8 | - [x] Figure out secrets of CoffeeSense and workspace setup 9 | - [x] Publishing types 10 | - [x] esbuild plugin 11 | - [x] Use esbuild for publishing 12 | - [x] main 13 | - [x] cli 14 | - [x] browser 15 | - [x] Update to @danielx/observable 16 | - [x] Update register 17 | - [x] Update to hera 0.7.3-pre.3 18 | -------------------------------------------------------------------------------- /types/ambient.d.ts: -------------------------------------------------------------------------------- 1 | import ObservableType from "@danielx/observable" 2 | import JadeletType from "./main" 3 | import assertType from "assert" 4 | 5 | declare global { 6 | // Test helpers 7 | var Event: typeof Event; 8 | var Jadelet: typeof JadeletType 9 | var Observable: typeof ObservableType; 10 | var assert: typeof assertType; 11 | var dispatch: (element: Element, eventName: string, options?: Object) => boolean 12 | var makeTemplate: typeof JadeletType["exec"]; 13 | 14 | interface Element { 15 | innerText: string 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/helper.civet: -------------------------------------------------------------------------------- 1 | assert from assert 2 | 3 | { JSDOM } from jsdom 4 | { window } := new JSDOM("") 5 | { Node, document } := window 6 | { Event } := window 7 | 8 | Object.assign global, { 9 | document 10 | window 11 | Node 12 | } 13 | 14 | Jadelet from ../source/index.civet 15 | { exec, Observable } := Jadelet 16 | 17 | Object.assign global, { 18 | assert 19 | Jadelet 20 | Observable 21 | 22 | dispatch(element: Element, eventName: string, options={}) 23 | element.dispatchEvent new Event eventName, options 24 | 25 | makeTemplate: exec 26 | } 27 | -------------------------------------------------------------------------------- /types/main.d.ts: -------------------------------------------------------------------------------- 1 | import Observable from "@danielx/observable"; 2 | import { Definitions, exec, JadeletParser } from "./types"; 3 | 4 | declare const Jadelet: { 5 | Observable: typeof Observable; 6 | parser: JadeletParser; 7 | _elementCleaners: any; 8 | dispose: any; 9 | retain: any; 10 | release: any; 11 | compile(source: string, opts?: { 12 | runtime?: string, 13 | exports?: string 14 | }): string; 15 | parse: JadeletParser["parse"]; 16 | exec: exec; 17 | define(definitions: Definitions): typeof Jadelet; 18 | } 19 | 20 | export = Jadelet 21 | -------------------------------------------------------------------------------- /test/indentation.civet: -------------------------------------------------------------------------------- 1 | describe "indentation", -> 2 | it "should work with somewhat flexible indentation for ease of use with 3 | template strings in js", -> 4 | indentedTemplate1 := """ 5 | p 6 | a(@click) Cool 7 | """ 8 | 9 | indentedTemplate2 := indentedTemplate1.replace(/^/, " ") 10 | 11 | T1 := makeTemplate indentedTemplate1 12 | T2 := makeTemplate indentedTemplate2 13 | 14 | el .= T1() 15 | assert.equal el.querySelector('a')?.textContent, "Cool" 16 | 17 | el = T2() 18 | assert.equal el.querySelector('a')?.textContent, "Cool" 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: ["push", "pull_request"] 2 | 3 | name: Build 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - uses: actions/checkout@v4 11 | 12 | - name: Setup Node.js 20.x 13 | uses: actions/setup-node@v4 14 | with: 15 | cache: yarn 16 | node-version: 20.x 17 | 18 | - name: Install and Test 19 | run: | 20 | yarn 21 | yarn build 22 | yarn test 23 | 24 | - name: Coveralls 25 | uses: coverallsapp/github-action@master 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /test/text.civet: -------------------------------------------------------------------------------- 1 | describe "text", -> 2 | it "should render a simple line of text", -> 3 | template := makeTemplate """ 4 | span 5 | | text 6 | """ 7 | 8 | element := template() 9 | assert.equal element.textContent, "text\n" 10 | 11 | it "should do inline text", -> 12 | template := makeTemplate """ 13 | p 14 | | hello I am a cool paragraph 15 | | with lots of text and stuff 16 | | ain't it rad? 17 | """ 18 | 19 | element := template() 20 | 21 | assert.equal element.textContent, """ 22 | hello I am a cool paragraph 23 | with lots of text and stuff 24 | ain't it rad? 25 | 26 | """ 27 | -------------------------------------------------------------------------------- /esbuild-plugin.js: -------------------------------------------------------------------------------- 1 | const { readFile } = require('fs/promises'); 2 | const { compile } = require('./'); 3 | 4 | module.exports = (options = {}) => ({ 5 | name: 'jadelet', 6 | setup: function (/** @type {import("esbuild").PluginBuild} */build) { 7 | return build.onLoad({ 8 | filter: /.\.jadelet$/ 9 | }, function (args) { 10 | return readFile(args.path, 'utf8').then(function (source) { 11 | return { 12 | contents: compile(source, options) 13 | }; 14 | }).catch(function (e) { 15 | return { 16 | errors: [ 17 | { 18 | text: e.message 19 | } 20 | ] 21 | }; 22 | }); 23 | }); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /test/compiler.civet: -------------------------------------------------------------------------------- 1 | fs from fs 2 | 3 | { compile } := Jadelet 4 | 5 | compileDirectory := (directory: string) -> 6 | fs.readdirSync(directory).forEach (file) -> 7 | if file.match /\.jadelet$/ 8 | it `compiles ${file}`, -> 9 | data := fs.readFileSync `${directory}/${file}`, "utf8" 10 | assert compile data 11 | return 12 | 13 | describe 'Compiler', -> 14 | describe 'samples', -> 15 | compileDirectory "test/samples" 16 | 17 | describe "exports", -> 18 | it "defaults to module.exports", -> 19 | compiled := compile "h1" 20 | assert compiled.match(/^module\.exports/) 21 | 22 | it "defaults to require('jadelet')", -> 23 | compiled := compile "h1" 24 | assert compiled.match(/require\('jadelet'\)/) 25 | -------------------------------------------------------------------------------- /test/input.civet: -------------------------------------------------------------------------------- 1 | describe "input", -> 2 | template := makeTemplate """ 3 | input(type="text" @value) 4 | """ 5 | 6 | it.skip "should maintain caret position", -> 7 | model := 8 | value: Observable "yolo" 9 | 10 | input := template(model) 11 | 12 | input.focus() 13 | input.selectionStart = 2 14 | 15 | assert.equal input.selectionStart, 2 16 | 17 | input.value = "yo2lo" 18 | // input.selectionStart = 3 19 | 20 | assert.equal input.selectionStart, 3 21 | 22 | input.onchange?(new Event("change")) 23 | 24 | assert.equal input.selectionStart, 3 25 | 26 | it "should start with given vaule", -> 27 | T := Jadelet.exec """ 28 | input(type="text" value="hello") 29 | """ 30 | 31 | el := T() 32 | assert.equal el.value, "hello" 33 | -------------------------------------------------------------------------------- /loader.mjs: -------------------------------------------------------------------------------- 1 | import { pathToFileURL } from 'url'; 2 | import "./register.js" 3 | 4 | const baseURL = pathToFileURL(process.cwd() + '/').href; 5 | const extensionsRegex = /\.jadelet$/; 6 | 7 | export async function resolve(specifier, context, defaultResolve) { 8 | const { parentURL = baseURL } = context; 9 | 10 | if (extensionsRegex.test(specifier)) { 11 | return { 12 | url: new URL(specifier, parentURL).href 13 | }; 14 | } 15 | 16 | // Let Node.js handle all other specifiers. 17 | return defaultResolve(specifier, context, defaultResolve); 18 | } 19 | 20 | export async function load(url, context, defaultLoad) { 21 | if (extensionsRegex.test(url)) { 22 | return { format: "commonjs" }; 23 | } 24 | 25 | // Let Node.js handle all other URLs. 26 | return defaultLoad(url, context, defaultLoad); 27 | } 28 | -------------------------------------------------------------------------------- /test/random_tags.civet: -------------------------------------------------------------------------------- 1 | describe "Random tags", -> 2 | template := makeTemplate """ 3 | div 4 | duder 5 | yolo(radical="true") 6 | sandwiches(@type) 7 | """ 8 | model := 9 | type: Observable "ham" 10 | 11 | it "should be have those tags and atrtibutes", -> 12 | element := template(model) 13 | 14 | assert element.querySelector "duder" 15 | assert element.querySelector("yolo")?.getAttribute("radical") 16 | assert.equal element.querySelector("sandwiches")?.getAttribute("type"), "ham" 17 | 18 | it "should reflect changes in observables", -> 19 | element := template(model) 20 | 21 | assert.equal element.querySelector("sandwiches")?.getAttribute("type"), "ham" 22 | model.type "pastrami" 23 | assert.equal element.querySelector("sandwiches")?.getAttribute("type"), "pastrami" 24 | -------------------------------------------------------------------------------- /test/elements.civet: -------------------------------------------------------------------------------- 1 | describe "elements", -> 2 | it "should render dom elements", -> 3 | div := document.createElement "div" 4 | div.innerText = "hello" 5 | 6 | T := Jadelet.exec """ 7 | section 8 | @el 9 | """ 10 | 11 | section := T 12 | el: div 13 | 14 | assert.equal section.children[0]?.innerText, "hello" 15 | assert section.children[0] instanceof window.HTMLDivElement 16 | 17 | it "should render 'view-like' elements", -> 18 | div := document.createElement "div" 19 | div.innerText = "hello" 20 | 21 | T := Jadelet.exec """ 22 | section 23 | @view 24 | """ 25 | 26 | section := T 27 | view: 28 | element: div 29 | 30 | assert.equal section.children[0]?.innerText, "hello" 31 | assert section.children[0] instanceof window.HTMLDivElement 32 | -------------------------------------------------------------------------------- /test/custom.civet: -------------------------------------------------------------------------------- 1 | describe "Custom Elements", -> 2 | it "should render custom elements", -> 3 | // TODO: Need to figure out observable bindings for attributes and children 4 | Jadelet.define 5 | Cool: (attributes, _children) -> 6 | el := document.createElement 'x-cool' 7 | 8 | Object.keys(attributes).map (k) -> 9 | [k, attributes[k]()] 10 | 11 | return el 12 | 13 | T .= Jadelet.exec """ 14 | Cool#myId.cl1.cl2(@rad cool wat="yo" @class @id @style) 15 | | he 16 | li Heyy 17 | @keeds 18 | """ 19 | 20 | assert.equal T({ 21 | rad: true 22 | class: Observable "c1" 23 | style: [ 24 | "color: green", 25 | {"font-size": "2rem"} 26 | ] 27 | }).tagName, "X-COOL" 28 | 29 | T = Jadelet.exec """ 30 | Cool(@id) 31 | """ 32 | 33 | assert.equal T({}).id, '' 34 | -------------------------------------------------------------------------------- /test/computed.civet: -------------------------------------------------------------------------------- 1 | describe "Computed", -> 2 | template := makeTemplate """ 3 | div 4 | h2 @name 5 | input(value=@first) 6 | input(value=@last) 7 | """ 8 | 9 | it "should compute automatically with the correct scope", -> 10 | model := 11 | name: -> 12 | @first() + " " + @last() 13 | first: Observable("Mr.") 14 | last: Observable("Doberman") 15 | 16 | element := template(model) 17 | 18 | assert.equal element.querySelector("h2")?.textContent, "Mr. Doberman" 19 | 20 | it "should work on special bindings", -> 21 | template := makeTemplate """ 22 | input(type='checkbox' checked=@checked) 23 | """ 24 | model := 25 | checked: -> 26 | @name() is "Duder" 27 | name: Observable "Mang" 28 | 29 | element := template(model) 30 | 31 | assert.equal element.checked, false 32 | model.name "Duder" 33 | assert.equal element.checked, true 34 | -------------------------------------------------------------------------------- /test/retain.civet: -------------------------------------------------------------------------------- 1 | describe "retain", -> 2 | it "should keep elements bound even when reused in the DOM", -> 3 | CanvasTemplate := makeTemplate """ 4 | canvas(@width @height) 5 | """ 6 | 7 | EditorTemplate := makeTemplate """ 8 | editor 9 | @title 10 | @canvas 11 | """ 12 | 13 | canvasModel := 14 | width: Observable 64 15 | height: Observable 64 16 | 17 | canvasElement := CanvasTemplate canvasModel 18 | Jadelet.retain canvasElement 19 | 20 | editorModel := 21 | title: Observable "yo" 22 | canvas: canvasElement 23 | 24 | EditorTemplate editorModel 25 | 26 | assert.equal canvasElement.getAttribute('height'), 64 27 | 28 | canvasModel.height 48 29 | assert.equal canvasElement.getAttribute('height'), 48 30 | 31 | editorModel.title "lo" 32 | 33 | canvasModel.height 32 34 | assert.equal canvasElement.getAttribute('height'), 32 35 | 36 | Jadelet.release canvasElement 37 | -------------------------------------------------------------------------------- /source/esbuild.civet: -------------------------------------------------------------------------------- 1 | type { Plugin } from esbuild 2 | Jadelet from ./index.civet 3 | 4 | type { PathLike } from fs 5 | { access, readFile } from fs/promises 6 | path from path 7 | 8 | exists := (p: PathLike) -> 9 | try 10 | await access(p) 11 | true 12 | catch 13 | false 14 | 15 | export default esbuildPlugin := (_options?: unknown): Plugin -> 16 | name: 'jadelet', 17 | setup: (build) -> 18 | build.onResolve { filter: /\/[^.]*$/ }, (r) -> 19 | {path: resolvePath, resolveDir} := r 20 | p := path.join(resolveDir, resolvePath + `.jadelet`) 21 | 22 | // see if a .coffee file exists 23 | found := await exists(p) 24 | if found 25 | return path: p 26 | 27 | return undefined 28 | 29 | build.onLoad { filter: /\.jadelet$/ }, (args) => 30 | source := await readFile(args.path, 'utf8') 31 | contents := Jadelet.compile source, 32 | runtime: 'require("jadelet")' 33 | 34 | return { contents, loader: 'js' } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "noImplicitAny": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "strictBindCallApply": true, 10 | "strictPropertyInitialization": true, 11 | "noImplicitThis": true, 12 | "useUnknownInCatchVariables": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "exactOptionalPropertyTypes": true, 17 | "noImplicitReturns": true, 18 | "noUncheckedIndexedAccess": true, 19 | "noImplicitOverride": true, 20 | "moduleResolution": "bundler", 21 | "allowImportingTsExtensions": true, 22 | "declaration": true, 23 | "esModuleInterop": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "resolveJsonModule": true, 26 | "emitDeclarationOnly": true, 27 | "outDir": "dist", 28 | "skipLibCheck": true 29 | }, 30 | "rootDir": "source" 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013,2021,2022 Daniel X Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/attributes.civet: -------------------------------------------------------------------------------- 1 | describe "Attributes", -> 2 | describe "@", -> 3 | it "should bind to the property with the same name", (done) -> 4 | template := makeTemplate """ 5 | button(@click) Test 6 | """ 7 | 8 | model := 9 | click: -> 10 | done() 11 | 12 | button := template(model) 13 | button.click() 14 | 15 | it "should work with multiple attributes", -> 16 | template := makeTemplate """ 17 | button(before="low" @type middle="mid" @yolo after="hi") Test 18 | """ 19 | 20 | model := 21 | type: "submit" 22 | yolo: "Hello" 23 | 24 | button := template(model) 25 | assert.equal button.getAttribute("type"), "submit" 26 | assert.equal button.getAttribute("yolo"), "Hello" 27 | 28 | it "shoud not be present when false or undefined", -> 29 | template := makeTemplate """ 30 | button(@disabled) Test 31 | """ 32 | 33 | model := 34 | disabled: Observable false 35 | 36 | button := template(model) 37 | assert.equal button.getAttribute("disabled"), undefined 38 | 39 | model.disabled true 40 | assert.equal button.getAttribute("disabled"), "true" 41 | -------------------------------------------------------------------------------- /test/memory-usage.civet: -------------------------------------------------------------------------------- 1 | describe "Memory usage", -> 2 | 3 | if global.gc 4 | usageTest := -> 5 | 6 | global.gc?() 7 | initialMemoryUsage := process.memoryUsage().heapUsed 8 | 9 | template := makeTemplate """ 10 | button(@click class=@selected) Test 11 | """ 12 | 13 | current := Observable null 14 | 15 | create := -> 16 | model := 17 | selected: -> 18 | if current() is model 19 | "selected" 20 | else 21 | "" 22 | click: -> 23 | 24 | template model 25 | 26 | i .= 0 27 | while i < 1000 28 | button := create() 29 | Jadelet.dispose(button) 30 | i += 1 31 | 32 | global.gc?() 33 | finalMemoryUsage := process.memoryUsage().heapUsed 34 | 35 | // console.log finalMemoryUsage, initialMemoryUsage 36 | // There's a surprising amount of variability in this memory usage number, but this seems 37 | // to trigger it every time when the call to _dispose is removed, so it may be decent at 38 | // detecting leaks 39 | delta := finalMemoryUsage - initialMemoryUsage 40 | target := 20000 41 | assert delta < target, `Memory used ${delta} not < ${target}` 42 | 43 | it "should remain stable even after many iterations", usageTest 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | 2.0.0 5 | ----- 6 | 7 | !!Breaking Changes!! 8 | 9 | - Remove `- ` and `= `. 10 | This simplifies the templates much further but is not necessarily a strict upgrade if you like using code in your templates. 11 | 12 | --- 13 | 14 | - Using hera for parsing 15 | - Dropped CoffeeScript dependency 16 | - Removed `- ` 17 | - Deprecated `h1= @content` in favor of `h1 @content` syntax 18 | - Faster 19 | - Parser and runtime combined in less than 10kb 20 | - No more implicit `options=` in `