@neocomp/vite-plugin 0.0.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/dist/.vite/manifest.json +8 -0
- package/dist/index.js +3 -0
- package/package.json +24 -0
- package/src/index.ts +168 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{resolve as c}from"node:path";import{readFile as b}from"node:fs/promises";const y=/^[^<>'"`=/\s]+/,S=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function h(i,t){return t+i.slice(t).match(/^\s*/)[0].length}function f(i,t){throw i==null?new SyntaxError(`unexpected end of input, expected "${t}"`):new SyntaxError(`unexpected token "${i}", expected "${t}"`)}function d(i,t,r){let l=i.slice(t).match(y)?.[0];return l||f(i[t],r),[l,t+l.length]}function E(i){let t=[{tag:"html",attrs:[],children:[]}],r="in_content";for(let[l,n]of i.entries()){let e=0;e:for(;n.length>e;){let a=t.at(-1);if(r==="in_do")e=h(n,e),n[e]==="/"&&(e+=1),n[e]!==">"&&f(n[e],">"),e+=1,r="in_content";else if(r==="in_attr_quoted")n[e]!=='"'&&n[e]!=="'"&&f(n[e],`'"'`),e+=1,r="in_attr";else if(r==="in_attr"){for(e=h(n,e);e<n.length&&n[e]!==">"&&n[e]!=="/";){let o,u;if([o,e]=d(n,e,"attribute name"),e=h(n,e),n[e]==="="){if(e+=1,e=h(n,e),e===n.length){a.attrs.push({attr:o,value:l});break e}if((n[e]==='"'||n[e]==="'")&&n.length===e+1){a.attrs.push({attr:o,value:l}),r="in_attr_quoted";break e}if(n[e]==='"'||n[e]==="'"){let p=n[e],g=n.indexOf(p,e+1);if(g===-1)throw new SyntaxError("unended string");u=n.slice(e+1,g),e=g+1}else[u,e]=d(n,e,"attribute value");let s=e;if(e=h(n,e),s===e&&e<n.length&&n[e]!==">"&&n[e]!=="/")throw new SyntaxError("expected whitespace")}else u="";a.attrs.push({attr:o,value:u})}n[e]==="/"?(e+=1,e=h(n,e),t.pop()):S.has(a.tag.toLowerCase())&&t.pop(),n[e]!==">"&&f(n[e],'">"'),e+=1,r="in_content"}else{let o=n.indexOf("<",e),u=n.slice(e,o===-1?n.length:o);if(u.length>0&&(typeof a.children.at(-1)=="string"?a.children[a.children.length-1]=a.children.at(-1)+u:a.children.push(u)),o===-1)break;if(e=o+1,n.length===e)a.children.push({type:"do",arg:l}),r="in_do";else if(n.slice(e,e+3)==="!--"){let s=n.indexOf("-->",e);if(s===-1)throw new SyntaxError("unended comment");e=s+3}else if(n[e]==="/"){e+=1;let s;if([s,e]=d(n,e,"tag"),t.length===1)throw new SyntaxError(`unexpected end tag "${s}" at root`);if(s!==a.tag)throw new SyntaxError(`expected end tag "${a.tag}" but got "${s}"`);e=h(n,e),n[e]!==">"&&f(n[e],'">"'),e+=1,t.pop()}else{let s;[s,e]=d(n,e,"tag");let p={tag:s,attrs:[],children:[]};a.children.push(p),t.push(p),r="in_attr"}}}l!==i.length-1&&r==="in_content"&&t.at(-1).children.push(l)}if(r!=="in_content")throw new SyntaxError('unexpected end of input, expected ">"');if(t.length!==1)throw new SyntaxError("end of input with unclosed tags");return t[0]}const $={include:["./src/"]};function q(i){let t={...$,...i};return t.include=t.include.map(r=>c(r)),{enforce:"pre",name:"neo-template",async load(r){let l=c(r),n;if(t.include.some(e=>l.startsWith(e))){try{n=await b(l,{encoding:"utf-8"})}catch{throw new Error(`neocomp: no file at path ${l}`)}return{code:O(n)}}}}}function v(i){return i.replaceAll(/\\(['"`ntr]|x..|u{[^}]+}|u....)/g,(t,r)=>r==="'"?"'":r==='"'?'"':r==="`"?"`":r==="n"?`
|
|
2
|
+
`:r==="t"?" ":r==="r"?"\r":r.startsWith("x")?String.fromCharCode(parseInt(r.slice(1),16)):r.startsWith("u{")?String.fromCodePoint(parseInt(r.slice(2,-1),16)):String.fromCodePoint(parseInt(r.slice(1),16)))}function _(i,t,r){let l=r;for(;;){if(l=i.indexOf(t,l),l===-1)return-1;let n=0,e=l-1;for(;e>=0&&i[e]==="\\";)n++,e--;if(n%2===0)return l;l++}}function m(i,t){t.result.push("`"),w(i,t,{part:r=>t.result.push(r),before_arg:()=>t.result.push("${"),after_arg:()=>t.result.push("}")}),t.result.push("`")}function w(i,t,r){for(t.ind+=1;;){let l=_(i,"`",t.ind);if(l===-1)throw new Error("unclosed template");let n=_(i,"${",t.ind);n>l&&(n=-1);let e=i.slice(t.ind,n==-1?l:n);if(r.part(e),n===-1){t.ind=l+1;break}let a=n+2;t.ind=a,r.before_arg();let o=1;for(;;){let u=i[t.ind];if(u==="{"&&(o+=1),u==="}"&&(o-=1),o===0)break;if((u==="'"||u==='"')&&(t.ind=_(i,u,t.ind+1),t.ind===-1))throw new Error("unclosed string");if(u==="`"){t.result.push(i.slice(a,t.ind)),i.slice(t.ind-4,t.ind)==="html"?k(i,t):m(i,t),a=t.ind;continue}t.ind+=1}t.result.push(i.slice(a,t.ind)),t.ind+=1,r.after_arg()}}function k(i,t){let r=[],l=t.chunks.length;t.result.push(`.__add(__neocomp_chunk_${l}, [`),t.chunks.push(null),w(i,t,{part:n=>r.push(v(n)),before_arg:()=>{},after_arg:()=>t.result.push(", ")}),t.result.push("])"),t.chunks[l]=`const __neocomp_chunk_${l} = ${JSON.stringify(E(r))};
|
|
3
|
+
`}function O(i,t){let r={ind:0,chunks:[],result:[]};for(;r.ind<i.length;){let l=_(i,"`",r.ind);if(r.result.push(i.slice(r.ind,l===-1?i.length:l)),l===-1)break;r.ind=l,i.slice(l-4,l)==="html"?k(i,r):m(i,r)}return r.chunks.join("")+r.result.join("")}export{q as neocomp};
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neocomp/vite-plugin",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "vite plugin for neocomp",
|
|
5
|
+
"author": "aliibrahim123",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"homepage": "https://aliibrahim123.github.io/neocomp.js/",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/aliibrahim123/neocomp.js/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/aliibrahim123/neocomp.js.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"vite": "^8.0.16",
|
|
19
|
+
"@neocomp/core": "^0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^26.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { parse } from '@neocomp/core/html-parser';
|
|
5
|
+
|
|
6
|
+
export interface Options {
|
|
7
|
+
include: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const defaultOptions: Options = {
|
|
11
|
+
include: ['./src/'],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function neocomp(options: Options): Plugin {
|
|
15
|
+
let opts = { ...defaultOptions, ...options };
|
|
16
|
+
opts.include = opts.include.map((dir) => resolve(dir));
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
enforce: 'pre',
|
|
20
|
+
name: 'neo-template',
|
|
21
|
+
async load(id) {
|
|
22
|
+
let path = resolve(id);
|
|
23
|
+
let file: string;
|
|
24
|
+
if (opts.include.some((dir) => path.startsWith(dir))) {
|
|
25
|
+
try {
|
|
26
|
+
file = await readFile(path, { encoding: 'utf-8' });
|
|
27
|
+
} catch (_error) {
|
|
28
|
+
throw new Error(`neocomp: no file at path ${path}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { code: compile_templates(file, opts) };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolve_escapes(str: string) {
|
|
38
|
+
return str.replaceAll(/\\(['"`ntr]|x..|u{[^}]+}|u....)/g, (_match, code) => {
|
|
39
|
+
if (code === "'") return "'";
|
|
40
|
+
if (code === '"') return '"';
|
|
41
|
+
if (code === '`') return '`';
|
|
42
|
+
if (code === 'n') return '\n';
|
|
43
|
+
if (code === 't') return '\t';
|
|
44
|
+
if (code === 'r') return '\r';
|
|
45
|
+
if (code.startsWith('x')) return String.fromCharCode(parseInt(code.slice(1), 16));
|
|
46
|
+
if (code.startsWith('u{')) return String.fromCodePoint(parseInt(code.slice(2, -1), 16));
|
|
47
|
+
return String.fromCodePoint(parseInt(code.slice(1), 16));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function find_unescaped(src: string, char: string, start: number): number {
|
|
52
|
+
let i = start;
|
|
53
|
+
while (true) {
|
|
54
|
+
i = src.indexOf(char, i);
|
|
55
|
+
if (i === -1) return -1;
|
|
56
|
+
|
|
57
|
+
let backslash_count = 0;
|
|
58
|
+
let j = i - 1;
|
|
59
|
+
while (j >= 0 && src[j] === '\\') {
|
|
60
|
+
backslash_count++;
|
|
61
|
+
j--;
|
|
62
|
+
}
|
|
63
|
+
if (backslash_count % 2 === 0) return i;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type Context = {
|
|
69
|
+
ind: number;
|
|
70
|
+
chunks: (string | null)[];
|
|
71
|
+
result: string[];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type TemplateCallbacks = {
|
|
75
|
+
part: (part: string) => void;
|
|
76
|
+
before_arg: () => void;
|
|
77
|
+
after_arg: () => void;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function skip_template(src: string, ctx: Context) {
|
|
81
|
+
ctx.result.push('`');
|
|
82
|
+
walk_template(src, ctx, {
|
|
83
|
+
part: (part) => ctx.result.push(part),
|
|
84
|
+
before_arg: () => ctx.result.push('${'),
|
|
85
|
+
after_arg: () => ctx.result.push('}'),
|
|
86
|
+
});
|
|
87
|
+
ctx.result.push('`');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function walk_template(src: string, ctx: Context, callbacks: TemplateCallbacks) {
|
|
91
|
+
ctx.ind += 1;
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
let end = find_unescaped(src, '`', ctx.ind);
|
|
95
|
+
if (end === -1) throw new Error('unclosed template');
|
|
96
|
+
let next_arg = find_unescaped(src, '${', ctx.ind);
|
|
97
|
+
if (next_arg > end) next_arg = -1;
|
|
98
|
+
|
|
99
|
+
let part = src.slice(ctx.ind, next_arg == -1 ? end : next_arg);
|
|
100
|
+
callbacks.part(part);
|
|
101
|
+
|
|
102
|
+
if (next_arg === -1) {
|
|
103
|
+
ctx.ind = end + 1;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let last_stop = next_arg + 2;
|
|
108
|
+
ctx.ind = last_stop;
|
|
109
|
+
callbacks.before_arg();
|
|
110
|
+
let curly_depth = 1;
|
|
111
|
+
|
|
112
|
+
while (true) {
|
|
113
|
+
let cur_char = src[ctx.ind];
|
|
114
|
+
if (cur_char === '{') curly_depth += 1;
|
|
115
|
+
if (cur_char === '}') curly_depth -= 1;
|
|
116
|
+
if (curly_depth === 0) break;
|
|
117
|
+
|
|
118
|
+
if (cur_char === "'" || cur_char === '"') {
|
|
119
|
+
ctx.ind = find_unescaped(src, cur_char, ctx.ind + 1);
|
|
120
|
+
if (ctx.ind === -1) throw new Error('unclosed string');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (cur_char === '`') {
|
|
124
|
+
ctx.result.push(src.slice(last_stop, ctx.ind));
|
|
125
|
+
|
|
126
|
+
if (src.slice(ctx.ind - 4, ctx.ind) === 'html') compile_template(src, ctx);
|
|
127
|
+
else skip_template(src, ctx);
|
|
128
|
+
|
|
129
|
+
last_stop = ctx.ind;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
ctx.ind += 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ctx.result.push(src.slice(last_stop, ctx.ind));
|
|
136
|
+
ctx.ind += 1;
|
|
137
|
+
callbacks.after_arg();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function compile_template(src: string, ctx: Context) {
|
|
142
|
+
let parts: string[] = [];
|
|
143
|
+
let id = ctx.chunks.length;
|
|
144
|
+
ctx.result.push(`.__add(__neocomp_chunk_${id}, [`);
|
|
145
|
+
ctx.chunks.push(null);
|
|
146
|
+
walk_template(src, ctx, {
|
|
147
|
+
part: (part) => parts.push(resolve_escapes(part)),
|
|
148
|
+
before_arg: () => {},
|
|
149
|
+
after_arg: () => ctx.result.push(', '),
|
|
150
|
+
});
|
|
151
|
+
ctx.result.push('])');
|
|
152
|
+
ctx.chunks[id] = `const __neocomp_chunk_${id} = ${JSON.stringify(parse(parts))};\n`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function compile_templates(src: string, _opts: Options): string {
|
|
156
|
+
let ctx: Context = { ind: 0, chunks: [], result: [] };
|
|
157
|
+
while (ctx.ind < src.length) {
|
|
158
|
+
let next_template = find_unescaped(src, '`', ctx.ind);
|
|
159
|
+
ctx.result.push(src.slice(ctx.ind, next_template === -1 ? src.length : next_template));
|
|
160
|
+
|
|
161
|
+
if (next_template === -1) break;
|
|
162
|
+
ctx.ind = next_template;
|
|
163
|
+
|
|
164
|
+
if (src.slice(next_template - 4, next_template) === 'html') compile_template(src, ctx);
|
|
165
|
+
else skip_template(src, ctx);
|
|
166
|
+
}
|
|
167
|
+
return ctx.chunks.join('') + ctx.result.join('');
|
|
168
|
+
}
|