打包远程脚本
通过将第三方脚本与您的应用打包来优化它们。
背景
当您在网站上使用来自其他站点的脚本时,会依赖另一个服务器来加载这些脚本。这可能会导致网站变慢,并引发安全和隐私方面的担忧。
常见问题
- 网站变慢,因为连接其他服务器需要时间。
- 如果其他服务器被黑,存在安全风险。
- 访客数据可能被其他服务器不当使用。
- 广告拦截器或隐私工具可能阻止这些脚本运行。
如何解决
通过打包这些脚本,您可以自行托管,从而避免这些问题,让您的网站运行更加顺畅。
工作原理
在构建过程中,会检查您的代码以查找需要打包的 useScript 实例。
当检测到需要打包的脚本时,它会被下载并保存为公共资源,路径为 /_scripts/[hash].js,其中 [hash] 表示脚本 URL 的哈希值。
关于打包的重要事项:
- 您需要为脚本 URL 和打包设置提供静态值。
// 好例子 - 静态值允许打包
useScript('https://example.com/script.js', {
bundle: true
})
// 坏例子 - 动态值阻止打包
useScript(scriptSrc, {
bundle: canBundle
})
// 好例子 - 脚本已打包
useScript('/_scripts/[hash].js', {})
// 坏例子 - 脚本未打包(保持不变)
useScript(scriptSrc, {
bundle: canBundle
})
- 如果原始脚本更改但 URL 未变化,浏览器缓存中的打包版本不会更新。为此,请使用带版本号的 URL 或添加防缓存查询参数。
使用方法
脚本可以单独打包,也可以通过特定设置全局打包。
脚本选项
决定是否打包某个脚本,使用 bundle 选项。
// 选择性打包这个脚本
useScript('https://example.com/script.js', {
bundle: true,
})
// 强制下载绕过缓存
useScript('https://example.com/script.js', {
bundle: 'force',
})
// 使用 scriptOptions 打包注册脚本
useScriptGoogleAnalytics({
id: 'GA_MEASUREMENT_ID',
scriptOptions: {
bundle: true
}
})
// 无缓存打包
useScriptGoogleAnalytics({
id: 'GA_MEASUREMENT_ID',
scriptOptions: {
bundle: 'force'
}
})
打包选项
bundle 选项支持以下值:
false- 不打包脚本(默认)true- 打包脚本,若有缓存则使用缓存版本'force'- 打包脚本,并强制下载绕过缓存
注意: 使用 'force' 会在每次构建时重新下载脚本,可能增加构建时间且安全性较低。
全局打包
通过 Nuxt 配置调整所有脚本的默认行为。下例中所有脚本默认启用打包。
nuxt.config.ts
export default defineNuxtConfig({
scripts: {
defaultScriptOptions: {
bundle: true,
}
}
})
构建时与运行时行为
理解打包发生时机及其对运行时行为的影响对有效使用至关重要。
构建时处理
打包通过静态代码分析在构建阶段完成:
// ✅ 构建时打包(静态值)
useScript('https://example.com/script.js', { bundle: true })
// ❌ 无法打包(动态值)
const scriptUrl = computed(() => getScriptUrl())
useScript(scriptUrl, { bundle: dynamic.value })
运行时行为
运行时,打包的脚本表现不同:
// 原始代码
useScript('https://example.com/script.js', { bundle: true })
// 构建转化后
useScript('/_scripts/abc123.js', {})
重要:一旦打包,运行时无法访问原始 URL。如果需要原始 URL 用于追踪或分析,请额外保存。
静态 URL 要求
打包转换器要求 完全静态的值:
// 静态字符串字面量
useScript('https://cdn.example.com/lib.js', { bundle: true })
// 静态模板字符串(无变量)
useScript(`https://cdn.example.com/lib.js`, { bundle: true })
// 在模块顶层定义的常量
const SCRIPT_URL = 'https://cdn.example.com/lib.js'
useScript(SCRIPT_URL, { bundle: true })
// 运行时变量
const url = getScriptUrl()
useScript(url, { bundle: true })
// 计算属性
const scriptUrl = computed(() => `https://cdn.example.com/${version.value}.js`)
useScript(scriptUrl, { bundle: true })
// 运行时环境变量
useScript(process.env.SCRIPT_URL, { bundle: true })
// Props 或响应式值
useScript(props.scriptUrl, { bundle: true })
手动注入模式
当自动打包不可用时,可以手动注入打包脚本:
手动打包解决方案
// 1. 使用静态URL构建时打包
const staticScript = useScript('https://cdn.example.com/static.js', {
bundle: true,
trigger: 'manual' // 不自动加载
})
// 2. 根据运行时逻辑条件加载
function loadScript() {
if (shouldLoadScript.value) {
staticScript.load()
}
}
// 3. 另一种方案:使用多个静态配置
const scriptVariants = {
dev: useScript('https://cdn.example.com/dev.js', { bundle: true, trigger: 'manual' }),
prod: useScript('https://cdn.example.com/prod.js', { bundle: true, trigger: 'manual' })
}
// 加载合适的版本
const currentScript = computed(() =>
isDev ? scriptVariants.dev : scriptVariants.prod
)
动态 URL 处理
对于真正动态的场景,可考虑以下方案:
动态 URL 策略
// 选项1:预先打包已知变体
const analytics = {
google: useScript('https://www.googletagmanager.com/gtag/js', { bundle: true }),
plausible: useScript('https://plausible.io/js/script.js', { bundle: true })
}
// 选项2:回退运行时加载
function loadDynamicScript(url: string) {
// 该脚本不会被打包,但运行时可用
return useScript(url, {
bundle: false, // 显式禁用
trigger: 'manual'
})
}
// 选项3:使用服务器端打包
// 将脚本内容存入包内并手动注入
const { $script } = useNuxtApp()
$script.add({
innerHTML: await $fetch('/api/dynamic-script-content'),
})
资源配置
利用配置中的 assets 选项定制脚本的打包和缓存行为。
nuxt.config.ts
export default defineNuxtConfig({
scripts: {
assets: {
prefix: '/_custom-script-path/',
cacheMaxAge: 86400000, // 1 天,单位毫秒
integrity: true, // 启用 SRI 哈希生成
}
}
})
可用选项
prefix- 打包脚本服务的自定义路径(默认:/_scripts/)cacheMaxAge- 打包脚本缓存时长,单位毫秒(默认:7 天)integrity- 启用自动 SRI(子资源完整性)哈希生成(默认:false)
缓存行为
打包系统使用两种不同的缓存策略:
- 构建时缓存:由
cacheMaxAge控制(默认 7 天)。超过该时长的脚本会在构建期间重新下载,确保新鲜度。 - 运行时缓存:打包脚本带有 1 年缓存头,因为它们是基于内容哈希命名的。
这种双重策略平衡了构建性能与浏览器缓存的可靠性。
子资源完整性 (SRI)
子资源完整性(SRI)是一项安全特性,确保脚本未被篡改。启用时,会为每个打包脚本计算加密哈希,并添加为 integrity 属性。
启用 SRI
nuxt.config.ts
export default defineNuxtConfig({
scripts: {
assets: {
integrity: true, // 默认使用 sha384
}
}
})
哈希算法
您可以指定哈希算法:
nuxt.config.ts
export default defineNuxtConfig({
scripts: {
assets: {
integrity: 'sha384', // 默认,安全性与大小之间的推荐平衡
// integrity: 'sha256', // 哈希更小
// integrity: 'sha512', // 最强安全性
}
}
})
工作流程
当启用 integrity:
- 构建时,会对每个打包脚本内容计算哈希
- 哈希存储在构建缓存中以便复用
- 脚本标签中注入
integrity属性 - 自动添加
crossorigin="anonymous"属性(浏览器要求)
<!-- 启用 integrity 后的输出示例 -->
<script src="/_scripts/abc123.js"
integrity="sha384-oqVuAfXRKap..."
crossorigin="anonymous"></script>
安全优势
- 篡改检测:若哈希不匹配,浏览器拒绝执行脚本
- CDN 被攻破保护:即使 CDN 被篡改,修改的脚本也不会被执行
- 构建时校验:哈希基于实际下载的内容计算,确保准确性