Scripting
HTML5
The <script> element is used to embed or reference executable code, typically JavaScript. It’s the fundamental way to add interactivity, dynamic behavior, and application logic to web pages. Understanding script loading strategies is crucial for optimal performance and user experience.
Interactive code playground requires JavaScript. Here's the code:
<!-- Inline JavaScript -->
<script>
console.log('Hello from inline script!');
alert('This script executes immediately');
</script>
<!-- External JavaScript -->
<script src="https://cdn.example.com/library.js"></script>
<!-- Modern ES Module -->
<script type="module">
import { greet } from './utils.js';
greet('World');
</script>
< script src = " path/to/file.js " ></ script >
< script type = " module " src = " path/to/module.js " ></ script >
Attribute Value Description srcURL Path to external script file typeMIME type text/javascript (default), module, or importmapasyncBoolean Download asynchronously, execute when ready deferBoolean Download asynchronously, execute after parsing crossoriginanonymous/use-credentialsCORS mode for cross-origin requests integrityHash Subresource Integrity hash for security nomoduleBoolean Execute only in browsers that don’t support modules referrerpolicyPolicy How much referrer information to send
The <script> element supports all global attributes .
Understanding script loading strategies is essential for performance:
HTML Parsing: ━━━━━━━━⏸️━━━━━━━━━━━━━━━━━━
HTML Parsing: ━━━━━━━━━━⏸️━━━━━━━━━━━━━━━
Script Download: ⬇️⬇️⬇️⬇️⬇️
HTML Parsing: ━━━━━━━━━━━━━━━━━━━━━━━━⏸️
Script Download: ⬇️⬇️⬇️⬇️⬇️
TYPE="module" (deferred by default):
HTML Parsing: ━━━━━━━━━━━━━━━━━━━━━━━━⏸️
Script Download: ⬇️⬇️⬇️⬇️⬇️
Interactive code playground requires JavaScript. Here's the code:
<!-- ❌ BLOCKS HTML PARSING -->
<p>Content before script</p>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<p>Content after script (appears after download + execution)</p>
<script>
// This runs immediately after lodash loads
console.log('Lodash version:', _.VERSION);
</script>
<style>
p {
padding: 1em;
background: #fee2e2;
border-left: 4px solid #dc2626;
margin: 0.5em 0;
}
</style> Characteristics:
⏸️ Pauses HTML parsing
⬇️ Downloads immediately
⚡ Executes immediately after download
❌ Blocks rendering
📍 Use: Only for critical scripts needed immediately
Interactive code playground requires JavaScript. Here's the code:
<!-- ✓ DOWNLOADS IN PARALLEL, EXECUTES WHEN READY -->
<p>Content before script</p>
<script async src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<p>Content after script (appears immediately)</p>
<script>
// ⚠️ This might run before lodash loads!
window.addEventListener('load', () => {
if (typeof _ !== 'undefined') {
console.log('Lodash loaded:', _.VERSION);
}
});
</script>
<style>
p {
padding: 1em;
background: #fef3c7;
border-left: 4px solid #f59e0b;
margin: 0.5em 0;
}
</style> Characteristics:
✅ Doesn’t block HTML parsing
⬇️ Downloads in parallel
⚡ Executes as soon as downloaded
⚠️ Execution order NOT guaranteed
📍 Use: Independent scripts (analytics, ads)
Interactive code playground requires JavaScript. Here's the code:
<!-- ✓ DOWNLOADS IN PARALLEL, EXECUTES AFTER PARSING -->
<p>Content before script</p>
<script defer src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<p>Content after script (appears immediately)</p>
<script>
// This runs after DOM is ready but before DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM ready, lodash available:', _.VERSION);
});
</script>
<style>
p {
padding: 1em;
background: #d1fae5;
border-left: 4px solid #10b981;
margin: 0.5em 0;
}
</style> Characteristics:
✅ Doesn’t block HTML parsing
⬇️ Downloads in parallel
⚡ Executes after parsing, before DOMContentLoaded
✅ Execution order preserved
📍 Use: Scripts that need the full DOM
Interactive code playground requires JavaScript. Here's the code:
<!-- ✓ MODULES ARE DEFERRED BY DEFAULT -->
<p>Content before module</p>
<script type="module">
// Modules automatically defer
// They execute after HTML parsing
console.log('Module executed after parsing');
// Can use import/export
const data = { message: 'Hello from ES Module!' };
console.log(data);
</script>
<p>Content after module (appears immediately)</p>
<style>
p {
padding: 1em;
background: #dbeafe;
border-left: 4px solid #3b82f6;
margin: 0.5em 0;
}
</style> Characteristics:
✅ Deferred by default
✅ Strict mode automatically
✅ Import/export support
✅ Separate scope (no global pollution)
📍 Use: Modern applications
Caution
Critical Decision Guide:
Default (no attribute) : Only for critical scripts that MUST run before page renders
async : Analytics, ads, or any independent functionality
defer : Scripts that need the DOM but don’t need to run immediately
type=“module” : Modern applications using ES6 imports/exports
Modern JavaScript with import/export syntax:
Interactive code playground requires JavaScript. Here's the code:
<script type="module">
// Inline module with imports
const greeting = 'Hello, World!';
function greet(name) {
return 'Hello, ' + name + '!';
}
console.log(greet('Developer'));
// Modules have their own scope
// Variables don't leak to global
const privateVar = 'Not accessible globally';
</script>
<script>
// This won't work - modules are scoped
try {
console.log(greeting);
} catch (e) {
console.log('Cannot access module variables:', e.message);
}
</script> Interactive code playground requires JavaScript. Here's the code:
<script type="module">
// Import from CDN using full URL
import confetti from 'https://cdn.skypack.dev/canvas-confetti@1.5.1';
// Use the imported function
const button = document.createElement('button');
button.textContent = '🎉 Celebrate!';
button.style.padding = '1em 2em';
button.style.fontSize = '1.2em';
button.style.cursor = 'pointer';
button.style.background = '#3b82f6';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '8px';
button.addEventListener('click', () => {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 }
});
});
document.body.appendChild(button);
</script> <!-- Define import map -->
< script type = " importmap " >
<!-- Use mapped imports -->
import dayjs from ' dayjs ' ;
console . log ( ' Lodash version: ' , _ . VERSION );
console . log ( ' Today: ' , dayjs () . format ( ' YYYY-MM-DD ' ));
SRI ensures that files from CDNs haven’t been tampered with:
Interactive code playground requires JavaScript. Here's the code:
<!-- WITHOUT SRI (⚠️ Not recommended for CDN) -->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js">
</script>
<!-- WITH SRI (✅ Recommended) -->
<script
src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-HASH-GOES-HERE"
crossorigin="anonymous">
</script>
<p style="padding: 1em; background: #d1fae5; border-radius: 4px;">
<strong>SRI Protection:</strong> If the file is modified on the CDN,
the browser will refuse to execute it, preventing potential attacks.
</p> curl -sS https://cdn.example.com/library.js | \
openssl dgst -sha384 -binary | \
curl -sS https://cdn.example.com/library.js | \
Online Tools:
<!-- Real-world example with popular libraries -->
src = " https://unpkg.com/react@18/umd/react.production.min.js "
integrity = " sha384-Qi4u... "
<!-- Alpine.js with SRI -->
src = " https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js "
<!-- Your application script -->
Controls CORS for script requests:
Interactive code playground requires JavaScript. Here's the code:
<!-- For SRI, crossorigin is REQUIRED -->
<script
src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-oVQ5NpKjEiwtgq9Z5FUOJqLQTZ8c0hcI4GYvWzkEfkrZB7gEQIV9a5xJCNdp5cE/"
crossorigin="anonymous">
</script>
<div style="padding: 1em; background: #f3f4f6; border-radius: 4px;">
<h3>crossorigin values:</h3>
<ul>
<li><code>anonymous</code>: No credentials sent</li>
<li><code>use-credentials</code>: Send credentials (cookies, certs)</li>
</ul>
<p><strong>Note:</strong> Required when using SRI or when you need
error details for cross-origin scripts.</p>
</div>
<!-- Critical inline styles -->
<!-- Preconnect to external domains -->
< link rel = " preconnect " href = " https://cdn.example.com " >
<!-- Critical JavaScript (rare) -->
// Only absolutely essential code
window . APP_CONFIG = { version: ' 1.0.0 ' };
<!-- Non-critical scripts at end with defer -->
< script defer src = " /js/app.js " ></ script >
< script defer src = " /js/analytics.js " ></ script >
<!-- Analytics (async - don't block) -->
< script async src = " https://www.google-analytics.com/analytics.js " ></ script >
<!-- Social media widgets (async) -->
< script async src = " https://platform.twitter.com/widgets.js " ></ script >
<!-- Ad scripts (async) -->
< script async src = " https://ads.example.com/script.js " ></ script >
<!-- Chat widget (load late with defer) -->
< script defer src = " https://chat.example.com/widget.js " ></ script >
Interactive code playground requires JavaScript. Here's the code:
<script>
// Load script only if needed
function loadScriptIfNeeded() {
if (!window.MyLibrary) {
const script = document.createElement('script');
script.src = 'https://cdn.example.com/library.js';
script.async = true;
script.onload = () => {
console.log('Library loaded!');
};
script.onerror = () => {
console.error('Failed to load library');
};
document.head.appendChild(script);
}
}
// Load on user interaction
document.getElementById('loadBtn').addEventListener('click', () => {
loadScriptIfNeeded();
});
</script>
<button id="loadBtn" style="padding: 1em; background: #3b82f6;
color: white; border: none; border-radius: 4px;
cursor: pointer;">
Load Library on Demand
</button>
Load scripts programmatically:
Interactive code playground requires JavaScript. Here's the code:
<button id="loadLib">Load Library</button>
<div id="status" style="margin-top: 1em; padding: 1em;
background: #f3f4f6; border-radius: 4px;">
Status: Not loaded
</div>
<script>
function loadScript(src, options = {}) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
// Set attributes
script.src = src;
if (options.async) script.async = true;
if (options.defer) script.defer = true;
if (options.type) script.type = options.type;
if (options.integrity) script.integrity = options.integrity;
if (options.crossOrigin) script.crossOrigin = options.crossOrigin;
// Handle load/error
script.onload = () => resolve(script);
script.onerror = () => reject(new Error('Failed to load: ' + src));
// Add to document
document.head.appendChild(script);
});
}
document.getElementById('loadLib').addEventListener('click', async () => {
const status = document.getElementById('status');
status.textContent = 'Status: Loading...';
status.style.background = '#fef3c7';
try {
await loadScript(
'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js',
{
async: true,
integrity: 'sha384-oVQ5NpKjEiwtgq9Z5FUOJqLQTZ8c0hcI4GYvWzkEfkrZB7gEQIV9a5xJCNdp5cE/',
crossOrigin: 'anonymous'
}
);
status.textContent = 'Status: ✓ Loaded! Lodash version: ' + _.VERSION;
status.style.background = '#d1fae5';
} catch (error) {
status.textContent = 'Status: ✗ Error - ' + error.message;
status.style.background = '#fee2e2';
}
});
</script>
<!-- Set CSP via meta tag -->
< meta http-equiv = " Content-Security-Policy "
content = " script-src 'self' https://cdn.example.com; " >
<!-- Or via HTTP header (preferred) -->
Content-Security-Policy: script-src 'self' https://cdn.example.com;
<!-- This will load (allowed origin) -->
< script src = " https://cdn.example.com/library.js " ></ script >
<!-- This will be blocked (different origin) -->
< script src = " https://malicious.com/script.js " ></ script >
<!-- Inline scripts blocked by default -->
// This won't execute without 'unsafe-inline' or nonce
<!-- Server generates unique nonce per request -->
< meta http-equiv = " Content-Security-Policy "
content = " script-src 'nonce-abc123xyz'; " >
<!-- Inline script with matching nonce -->
< script nonce = " abc123xyz " >
// This executes because nonce matches
console . log ( ' Allowed with nonce! ' );
<!-- Without nonce, blocked -->
<!-- Allow specific inline scripts by hash -->
< meta http-equiv = " Content-Security-Policy "
content = " script-src 'sha256-HASH-OF-SCRIPT'; " >
<!-- Calculate hash of this exact script -->
console . log ( ' Specific allowed script ' );
<!-- Different script, blocked -->
console . log ( ' Different script, blocked ' );
Interactive code playground requires JavaScript. Here's the code:
<div style="padding: 1em; background: #fee2e2; border-radius: 4px;
border-left: 4px solid #dc2626;">
<h3>❌ DANGEROUS - Don't Do This:</h3>
<pre style="background: #7f1d1d; color: #fecaca; padding: 1em;
border-radius: 4px;">
// User input directly in script
const userInput = '<?php echo $_GET["name"]; ?>';
document.write(userInput); // XSS vulnerability!
// Dynamic script creation from user input
const script = document.createElement('script');
script.innerHTML = userData; // DANGEROUS!
</pre>
</div>
<div style="padding: 1em; background: #d1fae5; border-radius: 4px;
border-left: 4px solid #10b981; margin-top: 1em;">
<h3>✅ SAFE - Do This Instead:</h3>
<pre style="background: #064e3b; color: #d1fae5; padding: 1em;
border-radius: 4px;">
// Sanitize and validate user input
const userInput = DOMPurify.sanitize(input);
// Use textContent, not innerHTML for text
element.textContent = userInput;
// Use data attributes for data
element.dataset.value = userInput;
</pre>
</div>
Caution
Critical Security Rules:
Never insert user input directly into script tags
Use CSP to restrict script sources
Validate and sanitize all user input
Use SRI for third-party scripts
Avoid eval() and Function() constructor
Prefer textContent over innerHTML
Interactive code playground requires JavaScript. Here's the code:
<script>
// Check if feature exists
if (!window.IntersectionObserver) {
// Load polyfill dynamically
const script = document.createElement('script');
script.src = 'https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver';
script.async = true;
document.head.appendChild(script);
console.log('Loading IntersectionObserver polyfill...');
} else {
console.log('IntersectionObserver supported natively!');
}
</script>
<div style="padding: 1em; background: #f3f4f6; border-radius: 4px;">
Check console for feature detection result
</div>
Interactive code playground requires JavaScript. Here's the code:
<!-- Modern browsers: use ES modules -->
<script type="module">
console.log('ES Modules supported!');
import('./modern-app.js');
</script>
<!-- Legacy browsers: use bundled script -->
<script nomodule src="/legacy-bundle.js">
// Only executes in browsers without module support
</script>
<div style="padding: 1em; background: #dbeafe; border-radius: 4px;">
<strong>nomodule attribute:</strong> Scripts with this attribute
only execute in browsers that don't support ES modules.
</div>
Universal Support
The <script> element is supported in all browsers:
Basic support : All browsers
async/defer : All modern browsers (IE10+)
type=“module” : Chrome 61+, Firefox 60+, Safari 11+, Edge 16+
integrity (SRI) : Chrome 45+, Firefox 43+, Safari 11.1+, Edge 17+
nomodule : Chrome 61+, Firefox 60+, Safari 11+, Edge 16+
Fallback : Use nomodule for legacy browser support.
Load strategy : Use defer for most scripts, async for independent scripts
Placement : Put scripts at end of <body> or use defer
Use modules : Prefer type="module" for modern applications
Security : Always use SRI for CDN scripts
CSP : Implement Content Security Policy
Performance : Minimize, bundle, and compress scripts
Lazy load : Load non-critical scripts on demand
Error handling : Always handle script loading errors
No inline handlers : Avoid onclick etc., use addEventListener
Validate inputs : Never trust user input in scripts
Learn More: