smol.js 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/dist/smol.js ADDED
@@ -0,0 +1,432 @@
1
+ function html(strings, ...values) {
2
+ return {
3
+ strings,
4
+ values,
5
+ _isTemplateResult: true
6
+ };
7
+ }
8
+ function isTemplateResult(value) {
9
+ return value && value._isTemplateResult === true;
10
+ }
11
+ function renderToString(template) {
12
+ let result = "";
13
+ for (let i = 0; i < template.strings.length; i++) {
14
+ result += template.strings[i];
15
+ if (i < template.values.length) {
16
+ const value = template.values[i];
17
+ result += stringifyValue(value);
18
+ }
19
+ }
20
+ return result;
21
+ }
22
+ function stringifyValue(value) {
23
+ if (value == null) {
24
+ return "";
25
+ }
26
+ if (isTemplateResult(value)) {
27
+ return renderToString(value);
28
+ }
29
+ if (Array.isArray(value)) {
30
+ return value.map(stringifyValue).join("");
31
+ }
32
+ if (typeof value === "boolean") {
33
+ return value ? "" : "";
34
+ }
35
+ if (typeof value === "function") {
36
+ return "";
37
+ }
38
+ return escapeHtml(String(value));
39
+ }
40
+ function escapeHtml(unsafe) {
41
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
42
+ }
43
+ function render(template, container) {
44
+ const existingStyles = Array.from(container.querySelectorAll("style"));
45
+ let html2 = "";
46
+ const markers = [];
47
+ let markerIndex = 0;
48
+ for (let i = 0; i < template.strings.length; i++) {
49
+ const str = template.strings[i];
50
+ html2 += str;
51
+ if (i < template.values.length) {
52
+ const value = template.values[i];
53
+ const lastPart = str.trim();
54
+ if (lastPart.endsWith("@click=") || lastPart.endsWith("@change=") || lastPart.includes("@")) {
55
+ markers.push({ index: markerIndex, value, type: "event" });
56
+ html2 += `"__smol_event_${markerIndex}__"`;
57
+ markerIndex++;
58
+ } else if (lastPart.endsWith("?disabled=") || lastPart.endsWith("?checked=") || lastPart.includes("?")) {
59
+ markers.push({ index: markerIndex, value, type: "boolean" });
60
+ html2 += `"__smol_bool_${markerIndex}__"`;
61
+ markerIndex++;
62
+ } else {
63
+ html2 += stringifyValue(value);
64
+ }
65
+ }
66
+ }
67
+ container.innerHTML = html2;
68
+ existingStyles.forEach((style) => {
69
+ container.insertBefore(style, container.firstChild);
70
+ });
71
+ markers.forEach((marker) => {
72
+ if (marker.type === "event") {
73
+ const markerAttr = `__smol_event_${marker.index}__`;
74
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
75
+ let node;
76
+ while (node = walker.nextNode()) {
77
+ const element = node;
78
+ for (const attr of Array.from(element.attributes)) {
79
+ if (attr.value === markerAttr) {
80
+ const eventName = attr.name.replace("@", "");
81
+ element.removeAttribute(attr.name);
82
+ if (typeof marker.value === "function") {
83
+ element.addEventListener(eventName, marker.value);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ } else if (marker.type === "boolean") {
89
+ const markerAttr = `__smol_bool_${marker.index}__`;
90
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
91
+ let node;
92
+ while (node = walker.nextNode()) {
93
+ const element = node;
94
+ for (const attr of Array.from(element.attributes)) {
95
+ if (attr.value === markerAttr) {
96
+ const attrName = attr.name.replace("?", "");
97
+ element.removeAttribute(attr.name);
98
+ if (marker.value) {
99
+ element.setAttribute(attrName, "");
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ });
106
+ }
107
+ function smolComponent(config) {
108
+ const {
109
+ tag,
110
+ mode = "open",
111
+ observedAttributes = [],
112
+ styles = "",
113
+ template,
114
+ connected,
115
+ disconnected,
116
+ attributeChanged
117
+ } = config;
118
+ if (!tag.includes("-")) {
119
+ throw new Error(`Custom element tag names must contain a hyphen: "${tag}"`);
120
+ }
121
+ class SmolCustomElement extends HTMLElement {
122
+ static get observedAttributes() {
123
+ return observedAttributes;
124
+ }
125
+ constructor() {
126
+ super();
127
+ if (!this.shadowRoot) {
128
+ this.attachShadow({ mode });
129
+ }
130
+ if (styles && this.shadowRoot) {
131
+ const styleElement = document.createElement("style");
132
+ styleElement.textContent = styles;
133
+ this.shadowRoot.appendChild(styleElement);
134
+ }
135
+ }
136
+ connectedCallback() {
137
+ if (connected) {
138
+ connected.call(this);
139
+ }
140
+ this.render();
141
+ }
142
+ disconnectedCallback() {
143
+ if (disconnected) {
144
+ disconnected.call(this);
145
+ }
146
+ }
147
+ attributeChangedCallback(name, oldValue, newValue) {
148
+ if (this.isConnected) {
149
+ this.render();
150
+ }
151
+ if (attributeChanged) {
152
+ attributeChanged.call(this, name, oldValue, newValue);
153
+ }
154
+ }
155
+ /**
156
+ * Render the component template
157
+ */
158
+ render() {
159
+ if (!template || !this.shadowRoot) {
160
+ return;
161
+ }
162
+ const ctx = {
163
+ emit: this.emit.bind(this),
164
+ render: this.render.bind(this),
165
+ // Add element as context (for accessing attributes, etc.)
166
+ element: this
167
+ };
168
+ const result = template.call(this, ctx);
169
+ if (!result) {
170
+ return;
171
+ }
172
+ if (typeof result === "string") {
173
+ this.shadowRoot.innerHTML = result;
174
+ } else if (isTemplateResult(result)) {
175
+ render(result, this.shadowRoot);
176
+ }
177
+ }
178
+ /**
179
+ * Emit a custom event
180
+ */
181
+ emit(name, detail) {
182
+ this.dispatchEvent(new CustomEvent(name, {
183
+ detail,
184
+ bubbles: true,
185
+ composed: true
186
+ }));
187
+ }
188
+ }
189
+ SmolCustomElement._smolConfig = config;
190
+ SmolCustomElement._smolTag = tag;
191
+ if (!customElements.get(tag)) {
192
+ customElements.define(tag, SmolCustomElement);
193
+ }
194
+ return SmolCustomElement;
195
+ }
196
+ const serviceRegistry = /* @__PURE__ */ new Map();
197
+ function smolService(config) {
198
+ const { name, factory, singleton = true } = config;
199
+ if (singleton) {
200
+ if (serviceRegistry.has(name)) {
201
+ return serviceRegistry.get(name);
202
+ }
203
+ const instance = factory();
204
+ serviceRegistry.set(name, instance);
205
+ return instance;
206
+ }
207
+ return factory();
208
+ }
209
+ function inject(name) {
210
+ if (!serviceRegistry.has(name)) {
211
+ throw new Error(`Service "${name}" not found. Did you forget to create it with smolService()?`);
212
+ }
213
+ return serviceRegistry.get(name);
214
+ }
215
+ function clearServices() {
216
+ serviceRegistry.clear();
217
+ }
218
+ function smolSignal(initialValue) {
219
+ let _value = initialValue;
220
+ const _subscribers = /* @__PURE__ */ new Set();
221
+ const signal = {
222
+ get value() {
223
+ return _value;
224
+ },
225
+ set value(newValue) {
226
+ if (_value !== newValue) {
227
+ _value = newValue;
228
+ _subscribers.forEach((fn) => fn(_value));
229
+ }
230
+ },
231
+ subscribe(fn) {
232
+ _subscribers.add(fn);
233
+ return () => {
234
+ _subscribers.delete(fn);
235
+ };
236
+ },
237
+ _subscribers
238
+ };
239
+ return signal;
240
+ }
241
+ function computed(fn) {
242
+ const signal = smolSignal(fn());
243
+ return signal;
244
+ }
245
+ function effect(fn) {
246
+ fn();
247
+ return () => {
248
+ };
249
+ }
250
+ function smolState(initialValue) {
251
+ const _subscribers = /* @__PURE__ */ new Set();
252
+ const notify = () => {
253
+ _subscribers.forEach((fn) => fn());
254
+ };
255
+ const data = new Proxy(initialValue, {
256
+ set(target, property, value) {
257
+ const oldValue = target[property];
258
+ if (oldValue !== value) {
259
+ target[property] = value;
260
+ notify();
261
+ }
262
+ return true;
263
+ },
264
+ deleteProperty(target, property) {
265
+ if (property in target) {
266
+ delete target[property];
267
+ notify();
268
+ }
269
+ return true;
270
+ }
271
+ });
272
+ const state = {
273
+ data,
274
+ subscribe(fn) {
275
+ _subscribers.add(fn);
276
+ return () => {
277
+ _subscribers.delete(fn);
278
+ };
279
+ },
280
+ _subscribers
281
+ };
282
+ return state;
283
+ }
284
+ function css(strings, ...values) {
285
+ let result = "";
286
+ for (let i = 0; i < strings.length; i++) {
287
+ result += strings[i];
288
+ if (i < values.length) {
289
+ result += String(values[i]);
290
+ }
291
+ }
292
+ return result.trim();
293
+ }
294
+ function renderComponentToString(ComponentClass, attributes = {}) {
295
+ const instance = new ComponentClass();
296
+ Object.entries(attributes).forEach(([key, value]) => {
297
+ instance.setAttribute(key, value);
298
+ });
299
+ const tagName = ComponentClass._smolTag || "unknown-element";
300
+ const config = ComponentClass._smolConfig;
301
+ const styles = (config == null ? void 0 : config.styles) || "";
302
+ if (config == null ? void 0 : config.connected) {
303
+ config.connected.call(instance);
304
+ }
305
+ let templateHTML = "";
306
+ if (config == null ? void 0 : config.template) {
307
+ const ctx = {
308
+ emit: instance.emit.bind(instance),
309
+ render: () => {
310
+ },
311
+ element: instance
312
+ };
313
+ const result = config.template.call(instance, ctx);
314
+ if (typeof result === "string") {
315
+ templateHTML = result;
316
+ } else if (result && result._isTemplateResult) {
317
+ templateHTML = renderToString(result);
318
+ }
319
+ }
320
+ if (globalThis.document && templateHTML) {
321
+ const template = document.createElement("template");
322
+ template.innerHTML = templateHTML;
323
+ expandNestedComponents(template.content);
324
+ templateHTML = template.innerHTML;
325
+ }
326
+ const shadowContent = `
327
+ ${styles ? `<style>${styles}</style>` : ""}
328
+ ${templateHTML}
329
+ `.trim();
330
+ const attrString = Object.entries(attributes).map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ");
331
+ return `
332
+ <${tagName}${attrString ? " " + attrString : ""}>
333
+ <template shadowrootmode="open">
334
+ ${shadowContent}
335
+ </template>
336
+ </${tagName}>
337
+ `.trim();
338
+ }
339
+ function escapeAttr(value) {
340
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
341
+ }
342
+ function ssr(components) {
343
+ return components.map(({ component, attributes }) => renderComponentToString(component, attributes)).join("\n");
344
+ }
345
+ function expandNestedComponents(node) {
346
+ const children = Array.from(node.childNodes);
347
+ children.forEach((child) => {
348
+ if (child.nodeType === 1) {
349
+ const element = child;
350
+ const tagName = element.tagName.toLowerCase();
351
+ if (globalThis.customElements && tagName.includes("-")) {
352
+ const ComponentClass = customElements.get(tagName);
353
+ if (ComponentClass) {
354
+ const attributes = {};
355
+ Array.from(element.attributes).forEach((attr) => {
356
+ attributes[attr.name] = attr.value;
357
+ });
358
+ const rendered = renderComponentToString(ComponentClass, attributes);
359
+ const temp = document.createElement("div");
360
+ temp.innerHTML = rendered;
361
+ const newChild = temp.firstElementChild;
362
+ if (newChild) {
363
+ element.replaceWith(newChild);
364
+ return;
365
+ }
366
+ }
367
+ }
368
+ expandNestedComponents(element);
369
+ }
370
+ });
371
+ }
372
+ function hydrateComponent(element) {
373
+ if (!element.shadowRoot) {
374
+ return;
375
+ }
376
+ element._hydrated = true;
377
+ attachEventListeners(element);
378
+ }
379
+ function attachEventListeners(element) {
380
+ if (!element.shadowRoot) return;
381
+ const walker = document.createTreeWalker(
382
+ element.shadowRoot,
383
+ NodeFilter.SHOW_ELEMENT
384
+ );
385
+ const elementsWithEvents = [];
386
+ let node;
387
+ while (node = walker.nextNode()) {
388
+ const el = node;
389
+ const events = /* @__PURE__ */ new Map();
390
+ for (const attr of Array.from(el.attributes)) {
391
+ if (attr.name.startsWith("data-smol-event-")) {
392
+ const eventName = attr.name.replace("data-smol-event-", "");
393
+ events.set(eventName, attr.value);
394
+ }
395
+ }
396
+ if (events.size > 0) {
397
+ elementsWithEvents.push({ element: el, events });
398
+ }
399
+ }
400
+ elementsWithEvents.forEach(({ element: el, events }) => {
401
+ events.forEach((handlerId, eventName) => {
402
+ console.warn(`Hydration: Found event listener ${eventName} but handler lookup not implemented`);
403
+ });
404
+ });
405
+ }
406
+ function hydrateAll() {
407
+ const allElements = document.querySelectorAll("*");
408
+ allElements.forEach((element) => {
409
+ if (element.tagName.includes("-") && element.shadowRoot) {
410
+ hydrateComponent(element);
411
+ }
412
+ });
413
+ }
414
+ export {
415
+ clearServices,
416
+ computed,
417
+ css,
418
+ effect,
419
+ html,
420
+ hydrateAll,
421
+ hydrateComponent,
422
+ inject,
423
+ isTemplateResult,
424
+ render,
425
+ renderComponentToString,
426
+ renderToString,
427
+ smolComponent,
428
+ smolService,
429
+ smolSignal,
430
+ smolState,
431
+ ssr
432
+ };
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Smol={})}(this,function(t){"use strict";function e(t){return t&&!0===t.t}function n(t){let e="";for(let n=0;n<t.strings.length;n++)e+=t.strings[n],n<t.values.length&&(e+=o(t.values[n]));return e}function o(t){return null==t?"":e(t)?n(t):Array.isArray(t)?t.map(o).join(""):"boolean"==typeof t||"function"==typeof t?"":String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function s(t,e){const n=Array.from(e.querySelectorAll("style"));let s="";const i=[];let r=0;for(let c=0;c<t.strings.length;c++){const e=t.strings[c];if(s+=e,c<t.values.length){const n=t.values[c],u=e.trim();u.endsWith("@click=")||u.endsWith("@change=")||u.includes("@")?(i.push({index:r,value:n,type:"event"}),s+=`"__smol_event_${r}__"`,r++):u.endsWith("?disabled=")||u.endsWith("?checked=")||u.includes("?")?(i.push({index:r,value:n,type:"boolean"}),s+=`"__smol_bool_${r}__"`,r++):s+=o(n)}}e.innerHTML=s,n.forEach(t=>{e.insertBefore(t,e.firstChild)}),i.forEach(t=>{if("event"===t.type){const n=`__smol_event_${t.index}__`,o=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let s;for(;s=o.nextNode();){const e=s;for(const o of Array.from(e.attributes))if(o.value===n){const n=o.name.replace("@","");e.removeAttribute(o.name),"function"==typeof t.value&&e.addEventListener(n,t.value)}}}else if("boolean"===t.type){const n=`__smol_bool_${t.index}__`,o=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let s;for(;s=o.nextNode();){const e=s;for(const o of Array.from(e.attributes))if(o.value===n){const n=o.name.replace("?","");e.removeAttribute(o.name),t.value&&e.setAttribute(n,"")}}}})}const i=new Map;function r(t){let e=t;const n=new Set;return{get value(){return e},set value(t){e!==t&&(e=t,n.forEach(t=>t(e)))},subscribe:t=>(n.add(t),()=>{n.delete(t)}),o:n}}function c(t,e={}){const o=new t;Object.entries(e).forEach(([t,e])=>{o.setAttribute(t,e)});const s=t.i||"unknown-element",i=t.u,r=(null==i?void 0:i.styles)||"";(null==i?void 0:i.connected)&&i.connected.call(o);let c="";if(null==i?void 0:i.template){const t={emit:o.emit.bind(o),render:()=>{},element:o},e=i.template.call(o,t);"string"==typeof e?c=e:e&&e.t&&(c=n(e))}if(globalThis.document&&c){const t=document.createElement("template");t.innerHTML=c,u(t.content),c=t.innerHTML}const l=`\n ${r?`<style>${r}</style>`:""}\n ${c}\n `.trim(),f=Object.entries(e).map(([t,e])=>`${t}="${function(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}(e)}"`).join(" ");return`\n<${s}${f?" "+f:""}>\n <template shadowrootmode="open">\n ${l}\n </template>\n</${s}>\n `.trim()}function u(t){Array.from(t.childNodes).forEach(t=>{if(1===t.nodeType){const e=t,n=e.tagName.toLowerCase();if(globalThis.customElements&&n.includes("-")){const t=customElements.get(n);if(t){const n={};Array.from(e.attributes).forEach(t=>{n[t.name]=t.value});const o=c(t,n),s=document.createElement("div");s.innerHTML=o;const i=s.firstElementChild;if(i)return void e.replaceWith(i)}}u(e)}})}function l(t){t.shadowRoot&&(t.l=!0,function(t){if(!t.shadowRoot)return;const e=document.createTreeWalker(t.shadowRoot,NodeFilter.SHOW_ELEMENT),n=[];let o;for(;o=e.nextNode();){const t=o,e=new Map;for(const n of Array.from(t.attributes))if(n.name.startsWith("data-smol-event-")){const t=n.name.replace("data-smol-event-","");e.set(t,n.value)}e.size>0&&n.push({element:t,events:e})}n.forEach(({element:t,events:e})=>{e.forEach((t,e)=>{})})}(t))}t.clearServices=function(){i.clear()},t.computed=function(t){return r(t())},t.css=function(t,...e){let n="";for(let o=0;o<t.length;o++)n+=t[o],o<e.length&&(n+=String(e[o]));return n.trim()},t.effect=function(t){return t(),()=>{}},t.html=function(t,...e){return{strings:t,values:e,t:!0}},t.hydrateAll=function(){document.querySelectorAll("*").forEach(t=>{t.tagName.includes("-")&&t.shadowRoot&&l(t)})},t.hydrateComponent=l,t.inject=function(t){if(!i.has(t))throw new Error(`Service "${t}" not found. Did you forget to create it with smolService()?`);return i.get(t)},t.isTemplateResult=e,t.render=s,t.renderComponentToString=c,t.renderToString=n,t.smolComponent=function(t){const{tag:n,mode:o="open",observedAttributes:i=[],styles:r="",template:c,connected:u,disconnected:l,attributeChanged:f}=t;if(!n.includes("-"))throw new Error(`Custom element tag names must contain a hyphen: "${n}"`);class a extends HTMLElement{static get observedAttributes(){return i}constructor(){if(super(),this.shadowRoot||this.attachShadow({mode:o}),r&&this.shadowRoot){const t=document.createElement("style");t.textContent=r,this.shadowRoot.appendChild(t)}}connectedCallback(){u&&u.call(this),this.render()}disconnectedCallback(){l&&l.call(this)}attributeChangedCallback(t,e,n){this.isConnected&&this.render(),f&&f.call(this,t,e,n)}render(){if(!c||!this.shadowRoot)return;const t={emit:this.emit.bind(this),render:this.render.bind(this),element:this},n=c.call(this,t);n&&("string"==typeof n?this.shadowRoot.innerHTML=n:e(n)&&s(n,this.shadowRoot))}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}}return a.u=t,a.i=n,customElements.get(n)||customElements.define(n,a),a},t.smolService=function(t){const{name:e,factory:n,singleton:o=!0}=t;if(o){if(i.has(e))return i.get(e);const t=n();return i.set(e,t),t}return n()},t.smolSignal=r,t.smolState=function(t){const e=new Set,n=()=>{e.forEach(t=>t())};return{data:new Proxy(t,{set:(t,e,o)=>(t[e]!==o&&(t[e]=o,n()),!0),deleteProperty:(t,e)=>(e in t&&(delete t[e],n()),!0)}),subscribe:t=>(e.add(t),()=>{e.delete(t)}),o:e}},t.ssr=function(t){return t.map(({component:t,attributes:e})=>c(t,e)).join("\n")},Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin for importing HTML templates as smol.js template functions
4
+ *
5
+ * This plugin allows you to write your component templates in separate .html files
6
+ * and import them into your components.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import template from './my-component.html?smol';
11
+ *
12
+ * smolComponent({
13
+ * tag: 'my-component',
14
+ * template(ctx) {
15
+ * return template(html);
16
+ * }
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function smolTemplatePlugin(): Plugin;
@@ -0,0 +1,73 @@
1
+ import { readFileSync } from 'node:fs';
2
+ /**
3
+ * Vite plugin for importing HTML templates as smol.js template functions
4
+ *
5
+ * This plugin allows you to write your component templates in separate .html files
6
+ * and import them into your components.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import template from './my-component.html?smol';
11
+ *
12
+ * smolComponent({
13
+ * tag: 'my-component',
14
+ * template(ctx) {
15
+ * return template(html);
16
+ * }
17
+ * });
18
+ * ```
19
+ */
20
+ export function smolTemplatePlugin() {
21
+ return {
22
+ name: 'vite-plugin-smol-templates',
23
+ enforce: 'pre',
24
+ async resolveId(id, importer) {
25
+ // Only handle .html files with ?smol query
26
+ if (id.includes('.html?smol')) {
27
+ const cleanId = id.replace('?smol', '');
28
+ const resolved = await this.resolve(cleanId, importer);
29
+ if (resolved) {
30
+ return resolved.id + '?smol';
31
+ }
32
+ }
33
+ return null;
34
+ },
35
+ load(id) {
36
+ // Check if this is an HTML template with ?smol query
37
+ if (!id.includes('.html?smol')) {
38
+ return null;
39
+ }
40
+ // Remove query parameter to get the actual file path
41
+ const filePath = id.replace(/\?smol$/, '');
42
+ try {
43
+ // Read the HTML file content
44
+ const htmlContent = readFileSync(filePath, 'utf-8');
45
+ // Transform the HTML into a JavaScript module
46
+ // The template will be a function that takes the html tagged template function
47
+ // and returns a TemplateResult
48
+ // We need to preserve ${} interpolations as actual template literal placeholders
49
+ // while escaping backticks in the HTML content
50
+ const escapedContent = htmlContent
51
+ .replace(/\\/g, '\\\\') // Escape backslashes
52
+ .replace(/`/g, '\\`'); // Escape backticks
53
+ // Create a function that uses 'with' to execute the template literal
54
+ // We use new Function to avoid strict mode limitations on 'with'
55
+ // and to allow dynamic variable resolution from the context
56
+ const templateBody = `with(context) { return html\`${escapedContent}\`; }`;
57
+ const code = `
58
+ export default function(html, context = {}) {
59
+ return new Function('html', 'context', ${JSON.stringify(templateBody)})(html, context);
60
+ }
61
+ `;
62
+ return {
63
+ code,
64
+ map: null
65
+ };
66
+ }
67
+ catch (error) {
68
+ this.error(`Failed to load HTML template: ${filePath}\n${error}`);
69
+ return null;
70
+ }
71
+ }
72
+ };
73
+ }
package/dist/vite.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { smolTemplatePlugin } from './vite-plugin-smol-templates.js';
package/dist/vite.js ADDED
@@ -0,0 +1,4 @@
1
+ // Export the Vite plugin for external HTML templates
2
+ // This is in a separate entry point to avoid bundling Node.js dependencies
3
+ // into the browser-facing library
4
+ export { smolTemplatePlugin } from './vite-plugin-smol-templates.js';
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "smol.js",
3
+ "version": "0.1.0",
4
+ "description": "Minimal Web Component library with zero dependencies",
5
+ "type": "module",
6
+ "main": "./dist/smol.umd.js",
7
+ "module": "./dist/smol.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/smol.js",
12
+ "require": "./dist/smol.umd.js",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./vite": {
16
+ "import": "./dist/vite.js",
17
+ "types": "./dist/vite.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "scripts": {
25
+ "build": "vite build && npm run build:types && npm run build:vite",
26
+ "build:types": "tsc --emitDeclarationOnly",
27
+ "build:vite": "tsc src/vite.ts src/vite-plugin-smol-templates.ts --outDir dist --module ESNext --target ES2020 --moduleResolution bundler --declaration",
28
+ "test": "vitest",
29
+ "dev": "vite build --watch"
30
+ },
31
+ "keywords": [
32
+ "web-components",
33
+ "custom-elements",
34
+ "shadow-dom",
35
+ "ssr",
36
+ "lightweight"
37
+ ],
38
+ "author": "",
39
+ "license": "MIT",
40
+ "devDependencies": {
41
+ "@types/node": "^20.0.0",
42
+ "terser": "^5.44.1",
43
+ "typescript": "^5.0.0",
44
+ "vite": "^5.0.0",
45
+ "vite-plugin-dts": "^3.0.0",
46
+ "vitest": "^1.0.0"
47
+ }
48
+ }