Skip to content

Commit

Permalink
Change the concept to similar to gtm
Browse files Browse the repository at this point in the history
  • Loading branch information
deeravenger committed Aug 22, 2024
1 parent 367df03 commit 2aace57
Show file tree
Hide file tree
Showing 11 changed files with 1,848 additions and 5,697 deletions.
6 changes: 0 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,9 @@ jobs:
- name: Run ESLint
run: npm run lint

- name: Run tests
run: npm test

- name: Build the project
run: npm run build

- name: Minify the build
run: npm run minify

- name: Check if there are changes to commit
id: check_changes
run: |
Expand Down
63 changes: 14 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ LilTag is a lightweight JavaScript tag management system designed to dynamically

## Features

- **Dynamic Script Loading**: Load scripts asynchronously, with defer, or standard loading.
- **Multiple Triggers**: Execute scripts based on page load, DOM ready, custom events, element visibility, or after a delay.
- **Flexible Configuration**: Easily configure how and when scripts are loaded via a JSON file or an inline configuration object.
- **Script Location Control**: Inject scripts into the `<head>`, at the top of the `<body>`, or at the bottom of the `<body>`.
- **Dynamic Script Injection**: Load scripts dynamically based on various triggers.
- **Inline Code Execution**: Execute inline JavaScript code on specified triggers.
- **Content Injection**: Inject complete HTML content, including scripts and noscript tags.
- **Customizable Loading**: Control where the content gets injected into the DOM (head, body top, or body bottom).
- **Event-Based Triggers**: Supports triggers such as page load, DOM ready, custom events, and more.
- **Caching**: Optional caching with customizable TTL (time-to-live) for the configurations.


## Installation
Expand Down Expand Up @@ -61,33 +63,8 @@ lilTag.init();
{
"id": "analytics",
"trigger": "pageLoad",
"script": "https://cdn.example.com/analytics.js",
"location": "head",
"loadingType": "async"
},
{
"id": "ads",
"trigger": "timeDelay",
"delay": 5000,
"script": "https://cdn.example.com/ads.js",
"location": "bodyBottom",
"loadingType": "async"
},
{
"id": "lazyLoadImages",
"trigger": "elementVisible",
"selector": ".lazy-load",
"script": "https://cdn.example.com/lazyload.js",
"location": "bodyBottom",
"loadingType": "standard"
},
{
"id": "customLogger",
"trigger": "customEvent",
"eventName": "userLoggedIn",
"code": "console.log('User logged in event detected.');",
"location": "bodyBottom",
"loadingType": "defer"
"content": "<script type=\"text/javascript\">console.log('Analytics script loaded.');</script>",
"location": "head"
}
]
}
Expand All @@ -102,17 +79,8 @@ const lilTag = new LilTag({
{
"id": "analytics",
"trigger": "pageLoad",
"script": "https://cdn.example.com/analytics.js",
"location": "head",
"loadingType": "async"
},
{
"id": "ads",
"trigger": "timeDelay",
"delay": 5000,
"script": "https://cdn.example.com/ads.js",
"location": "bodyBottom",
"loadingType": "async"
"content": "<script type=\"text/javascript\">console.log('Analytics script loaded.');</script>",
"location": "head"
}
]
});
Expand Down Expand Up @@ -140,18 +108,15 @@ Each tag in the configuration file or object should have the following propertie
- "timeDelay": Triggered after a specified delay once the page has loaded.
- "elementVisible": Triggered when a specified element becomes visible in the viewport.
- "customEvent": Triggered when a custom event is fired.
- **script**: (Optional) The URL of the script to be injected.
- **code**: (Optional) Inline JavaScript code to be executed.
- **content**: The full HTML content to inject, including script and noscript tags.
- **location**: Where to inject the script or execute the code. Options are:
- "head": Injects the script or code into the <head> of the document.
- "bodyTop": Injects the script or code at the top of the <body>.
- "bodyBottom": Injects the script or code at the bottom of the <body>.
- **delay**: (Optional) Used with the "timeDelay" trigger to specify the delay in milliseconds before the script or code is executed.
- **selector**: (Optional) A CSS selector for the element to observe. Required for the "elementVisible" trigger.
- **eventName**: (Optional) The name of the custom event to listen for. Required for the "customEvent" trigger.
- **loadingType**: (Optional) Specifies how the script should be loaded. Options are:
- "async": The script is loaded asynchronously and does not block page rendering. This is the default behavior.
- "defer": The script is loaded in parallel with other resources but is executed only after the HTML document is fully parsed.
- "standard": The script is loaded and executed immediately as it is encountered, blocking the parsing of the rest of the page.

Each tag must specify at least one of the following: script (URL) or code (inline JavaScript). The location determines where the script or code is inserted into the document.
## Contributing

Contributions are welcome! Please submit a pull request or create an issue to discuss your ideas or report bugs.
148 changes: 70 additions & 78 deletions dist/liltag.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,18 @@ var Trigger;
Trigger["ElementVisible"] = "elementVisible";
Trigger["CustomEvent"] = "customEvent";
})(Trigger || (Trigger = {}));
var ScriptLocation;
(function (ScriptLocation) {
ScriptLocation["Head"] = "head";
ScriptLocation["BodyTop"] = "bodyTop";
ScriptLocation["BodyBottom"] = "bodyBottom";
})(ScriptLocation || (ScriptLocation = {}));
var LoadingType;
(function (LoadingType) {
LoadingType["Async"] = "async";
LoadingType["Defer"] = "defer";
LoadingType["Standard"] = "standard";
})(LoadingType || (LoadingType = {}));
var ContentLocation;
(function (ContentLocation) {
ContentLocation["Head"] = "head";
ContentLocation["BodyTop"] = "bodyTop";
ContentLocation["BodyBottom"] = "bodyBottom";
})(ContentLocation || (ContentLocation = {}));
class LilTag {
constructor(config) {
this.config = config;
this.cacheEnabled = false;
this.cacheTTL = LilTag.CACHE_DEFAULT_TTL;
}
/**
* Enable caching with a specific TTL (time-to-live) in seconds.
* @param ttl - Time in seconds for which the cache is valid.
*/
enableCache(ttl = LilTag.CACHE_DEFAULT_TTL) {
if (ttl === 0) {
this.cacheEnabled = false;
Expand Down Expand Up @@ -106,9 +96,7 @@ class LilTag {
const cachedEntry = cacheData[url];
if (!cachedEntry)
return null;
// Calculate TTL in milliseconds
const ttlInMilliseconds = this.cacheTTL * 1000;
// Check if the cache has expired
if (Date.now() - cachedEntry.timestamp > ttlInMilliseconds) {
delete cacheData[url];
localStorage.setItem(LilTag.CACHE_KEY, JSON.stringify(cacheData));
Expand All @@ -124,10 +112,24 @@ class LilTag {
config.tags.forEach(tag => {
switch (tag.trigger) {
case Trigger.PageLoad:
this.executeTag(tag);
if (document.readyState === "complete") {
// Page has already loaded, execute immediately
this.executeTag(tag);
}
else {
// Attach event listener for page load
window.addEventListener("load", () => this.executeTag(tag));
}
break;
case Trigger.DomReady:
document.addEventListener("DOMContentLoaded", () => this.executeTag(tag));
if (document.readyState === "interactive" || document.readyState === "complete") {
// DOM is already ready, execute immediately
this.executeTag(tag);
}
else {
// Attach event listener for DOMContentLoaded
document.addEventListener("DOMContentLoaded", () => this.executeTag(tag));
}
break;
case Trigger.TimeDelay:
if (tag.delay !== undefined) {
Expand Down Expand Up @@ -158,66 +160,56 @@ class LilTag {
});
}
executeTag(tag) {
const loadingType = tag.loadingType || LoadingType.Async;
if (tag.script) {
this.injectScript(tag.script, tag.location, tag.id, loadingType);
}
else if (tag.code) {
this.executeCode(tag.code, tag.location, tag.id);
}
else {
console.warn(`Tag with ID "${tag.id}" has no script or code to execute.`);
}
this.injectContent(tag.content, tag.location, tag.id);
}
injectScript(url, location, tagId, loadingType = LoadingType.Async) {
const script = document.createElement("script");
script.src = url;
script.setAttribute(LilTag.DATA_ATTRIBUTE, tagId);
switch (loadingType) {
case LoadingType.Async:
script.async = true;
break;
case LoadingType.Defer:
script.defer = true;
break;
case LoadingType.Standard:
break;
default:
console.warn(`Unknown loading type "${loadingType}" - defaulting to "async".`);
script.async = true;
}
switch (location) {
case ScriptLocation.Head:
document.head.appendChild(script);
break;
case ScriptLocation.BodyTop:
document.body.insertBefore(script, document.body.firstChild);
break;
case ScriptLocation.BodyBottom:
document.body.appendChild(script);
break;
default:
console.warn(`Unknown location "${location}" - defaulting to body bottom.`);
document.body.appendChild(script);
injectContent(content, location, tagId) {
if (!content) {
console.warn(`Tag with ID "${tagId}" has no content to inject.`);
return;
}
}
executeCode(code, location, tagId) {
const script = document.createElement("script");
script.textContent = code;
script.setAttribute(LilTag.DATA_ATTRIBUTE, tagId);
switch (location) {
case ScriptLocation.Head:
document.head.appendChild(script);
break;
case ScriptLocation.BodyTop:
document.body.insertBefore(script, document.body.firstChild);
break;
case ScriptLocation.BodyBottom:
document.body.appendChild(script);
break;
default:
console.warn(`Unknown location "${location}" - defaulting to body bottom.`);
document.body.appendChild(script);
const tempDiv = document.createElement("div");
tempDiv.innerHTML = content.trim();
while (tempDiv.firstChild) {
const node = tempDiv.firstChild;
if (node instanceof HTMLElement) {
node.setAttribute(LilTag.DATA_ATTRIBUTE, tagId);
switch (location) {
case ContentLocation.Head:
if (node.nodeName === "SCRIPT" || node.nodeName === "NOSCRIPT") {
document.head.appendChild(node);
}
else {
console.warn("Injecting non-script content into <head> is not recommended.");
document.body.appendChild(node);
}
break;
case ContentLocation.BodyTop:
document.body.insertBefore(node, document.body.firstChild);
break;
case ContentLocation.BodyBottom:
document.body.appendChild(node);
break;
default:
console.warn(`Unknown location "${location}" - defaulting to body bottom.`);
document.body.appendChild(node);
}
}
else {
// If the node is not an HTMLElement, just append it to the correct location
switch (location) {
case ContentLocation.Head:
document.head.appendChild(node);
break;
case ContentLocation.BodyTop:
document.body.insertBefore(node, document.body.firstChild);
break;
case ContentLocation.BodyBottom:
document.body.appendChild(node);
break;
default:
document.body.appendChild(node);
}
}
}
}
}
Expand Down
Loading

0 comments on commit 2aace57

Please sign in to comment.