├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── continous-integration.yaml ├── .gitignore ├── .jshintrc ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── webpack-es5 │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── dummy.idomizer │ │ └── dummy.js │ ├── package.json │ └── webpack.config.js └── webpack-es6 │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ ├── dummy.idomizer │ └── dummy.js │ └── webpack.config.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── Options.ts ├── StringDictionary.ts ├── idomizer.ts └── plugins │ ├── babel-idomizer.ts │ ├── idomizer-loader.ts │ ├── idomizerify.ts │ └── utils.ts ├── test ├── idomizer.spec.ts └── plugins │ ├── babel-idomizer.spec.js │ ├── dummy.es6 │ └── idomizerify.spec.js ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js └── webpack.prd.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [*.json] 23 | indent_size = 2 24 | 25 | [*.yaml] 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: thibault.morin 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/continous-integration.yaml: -------------------------------------------------------------------------------- 1 | name: Continous Integration 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | scope: '@tmorin' 16 | # build 17 | - name: Install dependencies 18 | run: npm ci 19 | - name: Lint sources 20 | run: npm run lint 21 | - name: Build sources 22 | run: npm run build 23 | - name: Test library 24 | run: npm run test:lib 25 | env: 26 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 27 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 28 | - name: Test plugins 29 | run: npm run test:plugins 30 | - name: Build documentation 31 | run: npm run docs:build 32 | # publication 33 | - name: Publish package 34 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 35 | run: npm publish --tag latest 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | - name: Publish documentation 39 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 40 | uses: peaceiris/actions-gh-pages@v3 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | publish_dir: ./typedoc 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.tmp 3 | /dist 4 | /lib 5 | /node_modules 6 | /typedoc 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "evil": true 4 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.* 2 | /typedoc 3 | /test 4 | /npm-debug.log 5 | karma.* 6 | webpack.* 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.0.2](https://github.com/tmorin/idomizer/compare/v1.0.1...v1.0.2) (2019-10-17) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * upkg now loads the UMD module ([d3a4049](https://github.com/tmorin/idomizer/commit/d3a40491cf97153393f95971311ed4a00064ee39)) 11 | 12 | 13 | ## [1.0.1](https://github.com/tmorin/idomizer/compare/v1.0.0...v1.0.1) (2018-11-22) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * correct filename about TypeScript types ([3a63cef](https://github.com/tmorin/idomizer/commit/3a63cef)) 19 | 20 | 21 | 22 | 23 | # [1.0.0](https://github.com/tmorin/idomizer/compare/v0.10.2...v1.0.0) (2018-11-22) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thibault Morin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # idomizer 2 | 3 | [](https://github.com/tmorin/idomizer/actions/workflows/continous-integration.yaml) 4 | 5 | `idomizer` is an HTML template compiler providing an [incremental-dom] render factory. 6 | `idomizer` can be used at compile time (front end projects) or runtime time(back end projects). 7 | 8 | Versions and compatibilities: 9 | 10 | - idomizer <= 0.5 -> _incremental-dom_ 0.4 and below. 11 | - idomizer >= 0.6 -> _incremental-dom_ 0.5 and above. 12 | - idomizer >= 1.0.0 -> _incremental-dom_ 0.6 and above. 13 | 14 | ## Installation 15 | 16 | ```bash 17 | $ npm install idomizer 18 | ``` 19 | 20 | ```html 21 | 22 | 23 | 28 | ``` 29 | 30 | ### Babel 31 | 32 | A babel's plugin is available to compile an idomizer template into an [incremental-dom] render factory. 33 | 34 | See the [babel's plugins](https://babeljs.io/docs/en/plugins#syntax-plugins) page to get more information about plugins in babel. 35 | 36 | ```javascript 37 | { 38 | plugins: ['idomizer/lib/plugins/babel-idomizer.js'] 39 | } 40 | ``` 41 | 42 | Presently the plugin only support [ES6 Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) tagged with _idomizer_. 43 | 44 | For instance, 45 | ```javascript 46 | const template = idomizer`
will part of the light DOM
will part of the light DOM
1 value
61 | *some values
63 | *no values to display
65 | *1 value
77 | *some values
79 | *1 value
91 | *no values to display
93 | *true
.
155 | */
156 | skipExceptions?: boolean
157 | /**
158 | * If true element name having `-` or having an attribute `is` will be skipped.
159 | * By default `true`.
160 | */
161 | skipCustomElements?: boolean
162 | /**
163 | * The name of the variable exposing the data.
164 | */
165 | varDataName?: string
166 | /**
167 | * The name of the variable exposing the helpers.
168 | */
169 | varHelpersName?: string
170 | /**
171 | * The list of self closing elements. (http://www.w3.org/TR/html5/syntax.html#void-elements)
172 | */
173 | selfClosingElements?: string[]
174 | /**
175 | * The built in and custom tags.
176 | */
177 | tags?: TagHandlers
178 | }
179 |
--------------------------------------------------------------------------------
/src/StringDictionary.ts:
--------------------------------------------------------------------------------
1 | export interface StringDictionary {
2 | [type: string]: string
3 | }
4 |
--------------------------------------------------------------------------------
/src/idomizer.ts:
--------------------------------------------------------------------------------
1 | import {Parser} from 'htmlparser2';
2 | import {Options} from './Options';
3 | import {DefaultTagHandlers} from './Options';
4 | import {StringDictionary} from './StringDictionary';
5 | import {TagHandler} from './Options';
6 |
7 | function assign(...args) {
8 | return args.reduce(function (target, source) {
9 | return Object.keys(Object(source)).reduce((target, key) => {
10 | target[key] = source[key];
11 | return target;
12 | }, target);
13 | });
14 | }
15 |
16 | /**
17 | * The default implementation of the default tag handlers.
18 | */
19 | const DEFAULT_TAGS_HANDLERS: DefaultTagHandlers = {
20 | 'tpl-logger': {
21 | onopentag(name, attrs, key, statics, varArgs) {
22 | let level = statics.level || varArgs.level || 'log',
23 | content = statics.content || varArgs.content || '';
24 | return `console.${level}(${content});`;
25 | }
26 | },
27 | 'tpl-each': {
28 | onopentag(name, attrs, key, statics, varArgs) {
29 | let itemsName = statics.items || varArgs.items || `items`,
30 | itemName = statics.item || varArgs.item || `item`,
31 | indexName = statics.index || varArgs.index || `index`;
32 | return `(${itemsName} || []).forEach(function (${itemName}, ${indexName}) {`;
33 | },
34 | onclosetag() {
35 | return `});`;
36 | }
37 | },
38 | 'tpl-if': {
39 | onopentag(name, attrs, key, statics, varArgs) {
40 | let expression = statics.expression || varArgs.expression || 'false';
41 | return `if (${expression}) {`;
42 | },
43 | onclosetag() {
44 | return `}`;
45 | }
46 | },
47 | 'tpl-else-if': {
48 | onopentag(name, attrs, key, statics, varArgs) {
49 | let expression = statics.expression || varArgs.expression || 'false';
50 | return ` } else if (${expression}) { `;
51 | }
52 | },
53 | 'tpl-else': {
54 | onopentag() {
55 | return ` } else { `;
56 | }
57 | },
58 | 'tpl-text': {
59 | onopentag(name, attrs, key, statics, varArgs, options) {
60 | return inlineInterpolationEvaluator.inject(statics.value || varArgs.value, options);
61 | }
62 | },
63 | 'tpl-call': {
64 | onopentag(name, attrs, key, statics, varArgs, options) {
65 | let helperName = statics.name || varArgs.name;
66 | return `${options.varHelpersName}.${helperName}(${options.varDataName});`;
67 | }
68 | }
69 | };
70 |
71 | /**
72 | * The default options.
73 | */
74 | const DEFAULT_OPTIONS: Options = {
75 | pretty: false,
76 | ignoreStaticAttributes: false,
77 | interpolation: /{{([\s\S]+?)}}/gm,
78 | expression: /\[\[([\s\S]+?)]]/gm,
79 | attributeKey: 'tpl-key',
80 | attributeSkip: 'tpl-skip',
81 | skipExceptions: true,
82 | skipCustomElements: true,
83 | varDataName: 'data',
84 | varHelpersName: 'helpers',
85 | selfClosingElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'],
86 | tags: DEFAULT_TAGS_HANDLERS
87 | };
88 |
89 | function stringify(value = ''): string {
90 | return value.replace(/'/gim, '\\\'').replace(/\n/gi, '\\n');
91 | }
92 |
93 | function isSelfClosing(name = '', options = DEFAULT_OPTIONS): boolean {
94 | return options.selfClosingElements.indexOf(name) > -1;
95 | }
96 |
97 | function getFunctionName(name = '', options = DEFAULT_OPTIONS): string {
98 | return isSelfClosing(name, options) ? '_elementVoid' : '_elementOpen';
99 | }
100 |
101 | function append(body = '', line = '', options = DEFAULT_OPTIONS): string {
102 | if (line) {
103 | return body + (options.pretty ? '\n' : '') + line;
104 | }
105 | return body;
106 | }
107 |
108 | function createSafeJsBlock(value: string) {
109 | return `(function () { try { return ${value} } catch(e) { return '' } })()`;
110 | }
111 |
112 | /**
113 | * Configuration to transform an expression into a compliant JavaScript fragment.
114 | */
115 | interface Evaluator {
116 | /**
117 | * Appender between statements.
118 | */
119 | appender: string
120 | /**
121 | * To convert a text statements.
122 | * @param value the value
123 | */
124 | toText?: (value: string, options: Options) => string
125 | /**
126 | * To convert a js statements.
127 | * @param value the value
128 | * @param options the options
129 | */
130 | inject: (value: string, options: Options) => string
131 | }
132 |
133 | const attributeEvaluator: Evaluator = {
134 | appender: ' + ',
135 | toText: (value) => `'${stringify(value)}'`,
136 | inject: (value, options: Options) => options.skipExceptions ? createSafeJsBlock(value.trim()) : `(${value.trim()})`
137 | };
138 |
139 | const inlineInterpolationEvaluator: Evaluator = {
140 | appender: ' ',
141 | inject: (value, options) => options.skipExceptions ? `_text(${createSafeJsBlock(value.trim())});` : `_text(${value.trim()});`
142 | };
143 |
144 | const inlineExpressionEvaluator: Evaluator = {
145 | appender: ' ',
146 | inject: value => `${value}`
147 | };
148 |
149 | function evaluate(value: string, evaluator: Evaluator, regex: RegExp, options: Options): string {
150 | let js = [];
151 | let result;
152 | let lastIndex = 0;
153 | while ((result = regex.exec(value)) !== null) {
154 | let full = result[0];
155 | let group = result[1];
156 | let index = result.index;
157 | let before = value.substring(lastIndex, index);
158 | if (before) {
159 | js.push(evaluator.toText(before, options));
160 | }
161 | if (group.trim()) {
162 | js.push(evaluator.inject(group, options));
163 | }
164 | lastIndex = index + full.length;
165 | }
166 | let after = value.substring(lastIndex, value.length);
167 | if (after) {
168 | js.push(evaluator.toText(after, options));
169 | }
170 | return js.join(evaluator.appender);
171 | }
172 |
173 | function wrapExpressions(value: string, options: Options): string {
174 | return value.replace(options.interpolation, '').replace(options.expression, '');
175 | }
176 |
177 | function unwrapExpressions(value: string): string {
178 | return value.replace(//gim, '');
179 | }
180 |
181 | function checkSkipAttribute(attrs: StringDictionary = {}, options = DEFAULT_OPTIONS): boolean {
182 | return attrs.hasOwnProperty(options.attributeSkip) && attrs[options.attributeSkip] !== 'deactivated';
183 | }
184 |
185 | function checkIsAttribute(attrs: StringDictionary = {}, options = DEFAULT_OPTIONS): boolean {
186 | return options.skipCustomElements && attrs.hasOwnProperty('is') && attrs[options.attributeSkip] !== 'deactivated';
187 | }
188 |
189 | function parseAttributes(attrs: StringDictionary = {}, options = DEFAULT_OPTIONS) {
190 | const skip: boolean = checkSkipAttribute(attrs, options) || checkIsAttribute(attrs, options);
191 |
192 | const statics: StringDictionary = {};
193 | const varArgs: StringDictionary = {};
194 | Object.keys(attrs)
195 | .filter(key => [options.attributeSkip].indexOf(key) < 0)
196 | .forEach(function (key) {
197 | let value = unwrapExpressions(attrs[key]);
198 | if (value.search(options.interpolation) > -1 || options.ignoreStaticAttributes) {
199 | varArgs[key] = evaluate(value, attributeEvaluator, options.interpolation, options);
200 | } else {
201 | statics[key] = value;
202 | }
203 | });
204 |
205 | const key = statics[options.attributeKey] || varArgs[options.attributeKey];
206 | delete statics[options.attributeKey];
207 | delete varArgs[options.attributeKey];
208 |
209 | return {statics, varArgs, key, skip};
210 | }
211 |
212 | function varArgsToJs(varArgs = {}): string {
213 | let keys = Object.keys(varArgs);
214 | return keys.length > 0 ? (keys.map(key => `'${key}', ${varArgs[key]}`).join(', ')) : 'null';
215 | }
216 |
217 | function staticsToJs(statics = {}): string {
218 | let keys = Object.keys(statics);
219 | return keys.length > 0 ? `[${keys.map(key => `'${key}', '${stringify(statics[key])}'`).join(', ')}]` : 'null';
220 | }
221 |
222 | function checkCustomElement(name = '', attrs: StringDictionary = {}, options = DEFAULT_OPTIONS): boolean {
223 | return options.skipCustomElements && attrs[options.attributeSkip] !== 'deactivated' && name.indexOf('-') > -1;
224 | }
225 |
226 | /**
227 | * Compile the given HTML template into a function factory.
228 | *
229 | * If the incrementalDOM argument is provided, this function will return a render function.
230 | * The render function is used with IncrementalDOM.patch.
231 | *
232 | * If the incrementalDOM argument is not provided, this function will return a factory function.
233 | * The factory function requires the IncrementalDOM library as argument and return the render function..
234 | *
235 | * Basically, when the template is compiled at build time, the IncrementalDOM should not be given.
236 | * When the template is compiled at runtime, the IncrementalDOM should be given.
237 | *
238 | * @param html the template
239 | * @param options the options
240 | * @returns the function factory
241 | */
242 | export function compile(html = '', options: Options = DEFAULT_OPTIONS): Function {
243 | options = assign({}, DEFAULT_OPTIONS, options, {
244 | tags: assign({}, DEFAULT_TAGS_HANDLERS, options.tags)
245 | });
246 | let fnBody = '';
247 | let parser = new Parser({
248 | onopentag(name, attrs) {
249 | const {statics, varArgs, key, skip} = parseAttributes(attrs, options);
250 | if (options.tags[name]) {
251 | const tagHandler: TagHandler = options.tags[name];
252 | if (typeof tagHandler.onopentag === 'function') {
253 | fnBody = append(
254 | fnBody,
255 | tagHandler.onopentag(name, attrs, key, statics, varArgs, options),
256 | options
257 | );
258 | }
259 | } else {
260 | const fn = getFunctionName(name, options);
261 | fnBody = append(
262 | fnBody,
263 | `${fn}('${name}', ${key ? `${key}` : 'null'}, ${staticsToJs(statics)}, ${varArgsToJs(varArgs)});`,
264 | options
265 | );
266 | if (skip || checkCustomElement(name, attrs, options)) {
267 | fnBody = append(
268 | fnBody,
269 | `_skip();`,
270 | options
271 | );
272 | }
273 | }
274 | },
275 | onclosetag(name) {
276 | if (options.tags[name]) {
277 | const tagHandler: TagHandler = options.tags[name];
278 | if (typeof tagHandler.onclosetag === 'function') {
279 | fnBody = append(
280 | fnBody,
281 | tagHandler.onclosetag(name, options),
282 | options
283 | );
284 | }
285 | } else if (!isSelfClosing(name, options)) {
286 | fnBody = append(
287 | fnBody,
288 | `_elementClose('${name}');`,
289 | options
290 | );
291 | }
292 | },
293 | ontext(text) {
294 | if (text.search(options.expression) > -1) {
295 | fnBody = append(
296 | fnBody,
297 | `${evaluate(text, inlineExpressionEvaluator, options.expression, options)}`,
298 | options
299 | );
300 | } else if (text.search(options.interpolation) > -1) {
301 | fnBody = append(
302 | fnBody,
303 | `${evaluate(text, inlineInterpolationEvaluator, options.interpolation, options)}`,
304 | options
305 | );
306 | } else {
307 | fnBody = append(
308 | fnBody,
309 | `_text('${stringify(text)}');`,
310 | options
311 | );
312 | }
313 | }
314 | }, {
315 | xmlMode: false,
316 | decodeEntities: true,
317 | lowerCaseTags: false,
318 | lowerCaseAttributeNames: false,
319 | recognizeSelfClosing: true,
320 | recognizeCDATA: true
321 | });
322 |
323 | // wrap inline expression with a CDATA tag to allow inline javascript
324 | parser.parseComplete(wrapExpressions(html, options));
325 |
326 | let fnWrapper = `
327 | var _elementOpen = _i.elementOpen,
328 | _elementClose = _i.elementClose,
329 | _elementVoid = _i.elementVoid,
330 | _text = _i.text,
331 | _skip = _i.skip;
332 | return function (_data_) {
333 | var ${options.varHelpersName || 'helpers'} = _h,
334 | ${options.varDataName || 'data'} = _data_;
335 | ${fnBody}
336 | };
337 | `;
338 |
339 | // @ts-ignore
340 | return new Function(['_i', '_h'], fnWrapper);
341 | }
342 |
--------------------------------------------------------------------------------
/src/plugins/babel-idomizer.ts:
--------------------------------------------------------------------------------
1 | import {toStringFunction} from './utils';
2 |
3 | /**
4 | * @ignore
5 | */
6 | export default function ({types: t}) {
7 | return {
8 | visitor: {
9 | TaggedTemplateExpression(path, state) {
10 | if (path.node.tag.name === 'idomizer' && path.node.quasi.quasis.length === 1) {
11 | let factory = toStringFunction(path.node.quasi.quasis[0].value.cooked, state.opts);
12 | path.replaceWithSourceString(factory);
13 | }
14 | }
15 | }
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/src/plugins/idomizer-loader.ts:
--------------------------------------------------------------------------------
1 | import loaderUtils from 'loader-utils';
2 |
3 | const utils = require('./utils');
4 |
5 | /**
6 | * @ignore
7 | */
8 | module.exports = function (source) {
9 | const options = loaderUtils.getOptions(this);
10 | return 'module.exports = ' + utils.toStringFunction(source || '', options || {});
11 | };
12 |
13 |
--------------------------------------------------------------------------------
/src/plugins/idomizerify.ts:
--------------------------------------------------------------------------------
1 | import {makeStringTransform} from 'browserify-transform-tools';
2 | import {toStringFunction} from './utils';
3 |
4 | const options = {
5 | includeExtensions: ['.idomizer']
6 | };
7 |
8 | /**
9 | * @ignore
10 | */
11 | export default makeStringTransform('idomizerify', options, (content, transformOptions, done) => {
12 | done(null, 'module.exports = ' + toStringFunction(content, transformOptions.config));
13 | });
14 |
--------------------------------------------------------------------------------
/src/plugins/utils.ts:
--------------------------------------------------------------------------------
1 | var idomizer = require('../idomizer');
2 |
3 | /**
4 | * @ignore
5 | */
6 | export function toStringFunction(html, options) {
7 | return idomizer.compile(html, options).toString().replace('function anonymous', 'function');
8 | }
9 |
--------------------------------------------------------------------------------
/test/idomizer.spec.ts:
--------------------------------------------------------------------------------
1 | import {compile} from '../src/idomizer';
2 | import * as IncrementalDOM from 'incremental-dom';
3 | import {expect} from 'chai';
4 |
5 | describe('idomizer', () => {
6 | let sandbox;
7 |
8 | beforeEach(function () {
9 | sandbox = document.body.appendChild(document.createElement('div'));
10 | });
11 |
12 | it('should render a simple h1 with a static attribute', () => {
13 | const render = compile(`1 item
70 |items
72 |no items
75 |1 item
'); 79 | 80 | IncrementalDOM.patch(sandbox, render, {items: ['item0', 'item2']}); 81 | expect(sandbox.innerHTML.trim()).to.contain('items
'); 82 | 83 | IncrementalDOM.patch(sandbox, render, {items: []}); 84 | expect(sandbox.innerHTML.trim()).to.contain('no items
'); 85 | }); 86 | 87 | it('should handle conditional statements with inline statements', () => { 88 | const render = compile(` 89 | [[ if (data.items.length > 0 && data.items.length < 2) { ]] 90 |1 item
91 | [[ } else if (data.items.length > 1) { ]] 92 |items
93 | [[ } else { ]] 94 |no items
95 | [[ } ]] 96 | `)(IncrementalDOM); 97 | IncrementalDOM.patch(sandbox, render, {items: ['item0']}); 98 | expect(sandbox.innerHTML.trim()).to.contain('1 item
'); 99 | 100 | IncrementalDOM.patch(sandbox, render, {items: ['item0', 'item2']}); 101 | expect(sandbox.innerHTML.trim()).to.contain('items
'); 102 | 103 | IncrementalDOM.patch(sandbox, render, {items: []}); 104 | expect(sandbox.innerHTML.trim()).to.contain('no items
'); 105 | }); 106 | 107 | it('should use custom elements', () => { 108 | const render = compile(`strong textskipped content
strong text`)(IncrementalDOM); 130 | const render2 = compile(`strong text bisstrong text bis`)(IncrementalDOM); 131 | IncrementalDOM.patch(sandbox, render1); 132 | expect(sandbox.innerHTML.trim()).to.eq('strong textskipped content
strong text', 'render1'); 133 | 134 | IncrementalDOM.patch(sandbox, render2); 135 | expect(sandbox.innerHTML.trim()).to.eq('strong text bisskipped content
strong text bis', 'render2'); 136 | }); 137 | 138 | it('should skip content node of custom element', () => { 139 | const render = compile(`strong text bisskipped content
strong text`; 162 | IncrementalDOM.patch(sandbox, render); 163 | expect(sandbox.innerHTML.trim()).to.eq('strong text bisskipped content
strong text bis', 'render'); 164 | }); 165 | 166 | it('should not skip content node of custom element having is attribute - locally', () => { 167 | const render = compile(`strong text biscontent
strong text bis`)(IncrementalDOM); 168 | sandbox.innerHTML = `strong textskipped content
strong text`; 169 | IncrementalDOM.patch(sandbox, render); 170 | expect(sandbox.innerHTML.trim()).to.eq('strong text biscontent
strong text bis', 'render'); 171 | }); 172 | 173 | it('should not skip content node of custom element having is attribute - globally', () => { 174 | const render = compile(`strong text biscontent
strong text bis`, {skipCustomElements: false})(IncrementalDOM); 175 | sandbox.innerHTML = `strong textskipped content
strong text`; 176 | IncrementalDOM.patch(sandbox, render); 177 | expect(sandbox.innerHTML.trim()).to.eq('strong text biscontent
strong text bis', 'render'); 178 | }); 179 | 180 | it('should ignore static attributes', () => { 181 | const render = compile(` 182 |t {{ data.txtNode1 }} t {{ data.txtNode2 }} {{ }}
196 | `)(IncrementalDOM); 197 | IncrementalDOM.patch(sandbox, render1, { 198 | v1: 1, 199 | txtNode1: 'value1', 200 | txtNode2: 'value2', 201 | att1: 'a1', 202 | att2: 'a2' 203 | }); 204 | expect(sandbox.innerHTML.trim()).to.eq('YESt value1 t value2
', 'render1'); 205 | }); 206 | 207 | it('should ingore interpolation exception', () => { 208 | const render1 = compile(` 209 | 210 | `, {pretty: true, skipExceptions: true})(IncrementalDOM); 211 | IncrementalDOM.patch(sandbox, render1, { 212 | v1: 1, 213 | txtNode1: 'value1', 214 | txtNode2: 'value2', 215 | att1: 'a1', 216 | att2: 'a2' 217 | }); 218 | expect(sandbox.innerHTML.trim()).to.eq('t t value2
', 'render1'); 219 | }); 220 | 221 | }); 222 | -------------------------------------------------------------------------------- /test/plugins/babel-idomizer.spec.js: -------------------------------------------------------------------------------- 1 | const babelIdomizer = require('../../lib/plugins/babel-idomizer'); 2 | const {expect} = require('chai'); 3 | const babel = require('@babel/core'); 4 | 5 | describe('babel-idomizer', () => { 6 | 7 | it('should convert an idomizer file into a string function', (done) => { 8 | let options = { 9 | plugins: [[babelIdomizer, {skipExceptions: false}]] 10 | }; 11 | babel.transformFile('test/plugins/dummy.es6', options, (err, result) => { 12 | if (err) { 13 | return done(err); 14 | } 15 | expect(result.code).to.contain(`_elementOpen('h1', null, null, 'class', data.h1Class);`); 16 | expect(result.code).to.contain(`_text('\\n Hello\\n ');`); 17 | expect(result.code).to.contain(`_elementClose('h1');`); 18 | done(); 19 | }); 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /test/plugins/dummy.es6: -------------------------------------------------------------------------------- 1 | let template = idomizer` 2 |