vite-plugin-tannijs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +26 -0
- package/src/index.test.ts +45 -0
- package/src/index.ts +75 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @tannijs/vite-plugin
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-tannijs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Vite plugin for compiling .tanni single-file components",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Sebastijan Zindl",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./src/index.ts",
|
|
15
|
+
"types": "./src/index.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"tannijs-compiler": "0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"vite": ">=5.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { tanniPlugin } from './index';
|
|
4
|
+
|
|
5
|
+
describe('tanniPlugin', () => {
|
|
6
|
+
it('transforms .tanni files through compiler', async () => {
|
|
7
|
+
const plugin = tanniPlugin({ runtimeModule: 'tanni-runtime' });
|
|
8
|
+
const transformHook =
|
|
9
|
+
typeof plugin.transform === 'function' ? plugin.transform : plugin.transform?.handler;
|
|
10
|
+
expect(transformHook).toBeTypeOf('function');
|
|
11
|
+
const source = `
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
const value = () => 'ok';
|
|
14
|
+
</script>
|
|
15
|
+
<template>
|
|
16
|
+
<p>{{ value() }}</p>
|
|
17
|
+
</template>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const output = await transformHook?.call(
|
|
21
|
+
{} as never,
|
|
22
|
+
source,
|
|
23
|
+
'/demo/components/HelloWorld.tanni'
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(output).toBeTruthy();
|
|
27
|
+
const result = output as { code: string };
|
|
28
|
+
expect(result.code).toContain("import { createEffect, delegateEvents } from 'tanni-runtime';");
|
|
29
|
+
expect(result.code).toContain('function HelloWorld(__props = {})');
|
|
30
|
+
expect(result.code).toContain('document.createTextNode');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('ignores non-tanni files', async () => {
|
|
34
|
+
const plugin = tanniPlugin();
|
|
35
|
+
const transformHook =
|
|
36
|
+
typeof plugin.transform === 'function' ? plugin.transform : plugin.transform?.handler;
|
|
37
|
+
expect(transformHook).toBeTypeOf('function');
|
|
38
|
+
const output = await transformHook?.call(
|
|
39
|
+
{} as never,
|
|
40
|
+
'export const a = 1;',
|
|
41
|
+
'/demo/a.ts'
|
|
42
|
+
);
|
|
43
|
+
expect(output).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import type { Plugin } from 'vite';
|
|
3
|
+
|
|
4
|
+
import { compileSfc } from 'tannijs-compiler';
|
|
5
|
+
|
|
6
|
+
export interface TanniPluginOptions {
|
|
7
|
+
runtimeModule?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TANNI_EXTENSION = '.tanni';
|
|
11
|
+
const VIRTUAL_CSS_SUFFIX = '.tanni.css';
|
|
12
|
+
const VIRTUAL_CSS_PREFIX = '\0';
|
|
13
|
+
const DEFAULT_RUNTIME_MODULE = 'tannijs/internals';
|
|
14
|
+
|
|
15
|
+
export function tanniPlugin(options: TanniPluginOptions = {}): Plugin {
|
|
16
|
+
const cssCache = new Map<string, string>();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: 'tanni-plugin',
|
|
20
|
+
enforce: 'pre',
|
|
21
|
+
|
|
22
|
+
resolveId(source) {
|
|
23
|
+
if (source.endsWith(VIRTUAL_CSS_SUFFIX)) {
|
|
24
|
+
return VIRTUAL_CSS_PREFIX + source;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
load(id) {
|
|
30
|
+
if (id.startsWith(VIRTUAL_CSS_PREFIX) && id.endsWith(VIRTUAL_CSS_SUFFIX)) {
|
|
31
|
+
const realId = id.slice(VIRTUAL_CSS_PREFIX.length);
|
|
32
|
+
return cssCache.get(realId) ?? '';
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
transform(source, id) {
|
|
38
|
+
if (!id.endsWith(TANNI_EXTENSION)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const runtimeModule = options.runtimeModule ?? DEFAULT_RUNTIME_MODULE;
|
|
43
|
+
const result = compileSfc(source, {
|
|
44
|
+
id,
|
|
45
|
+
componentName: inferComponentName(id),
|
|
46
|
+
runtimeModule,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let code = result.code;
|
|
50
|
+
|
|
51
|
+
if (result.css.length > 0) {
|
|
52
|
+
const virtualCssId = id + '.css';
|
|
53
|
+
cssCache.set(virtualCssId, result.css);
|
|
54
|
+
code = `import ${JSON.stringify(virtualCssId)};\n${code}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
code,
|
|
59
|
+
map: null,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function inferComponentName(filePath: string): string {
|
|
66
|
+
const fileName = path.basename(filePath, TANNI_EXTENSION);
|
|
67
|
+
const sanitized = fileName.replace(/[^A-Za-z0-9_$]/g, '_');
|
|
68
|
+
if (sanitized.length === 0) {
|
|
69
|
+
return 'Component';
|
|
70
|
+
}
|
|
71
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
72
|
+
return `Component_${sanitized}`;
|
|
73
|
+
}
|
|
74
|
+
return sanitized;
|
|
75
|
+
}
|