【Vite插件】使用importmap添加cdn的一些思考
最近在优化项目的打包,一直有在想是否有办法将第三方库换成cdn
,这样包的加载、缓存都可以得到很大的改善,并且也可以减少打包后项目的体积,节省服务器资源等
但由于项公司项目是 运行在沙箱 中,开发环境下无法加载外部资源
一直都束手无策
后来偶然想到可以在开发环境使用npm
包,然后在构建时替换成CDN
;嗯,倒是有一点可行性
1、src="CDN"
添加CDN
构建工具:vite
思路:在构建时,将某些第三方包排除,不对其进行打包;编写一个vite插件,添加CDN到html
文件
<script type="module" src="cdn"></script>
1.1 排除第三方模块
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
external: ['vue'] // 将vue排除在bundle外部
}
}
})
1.2 编写vite
插件
查阅文档,vite
提供了一个钩子:transformIndexHtml
- 一个转化
html
文件的专用钩子 return
转换后的html
字符串
function addCDNsPlugin(options) {
const { cdnUrls } = options;
return {
name: 'vite-plugin-add-importmap',
transformIndexHtml(html) {
let scriptTag = ''
cdnUrls.forEach(url => {
scriptTag += `
<script type="module" src="${url}"></script>
`;
})
// 将其拼接在title标签后面
const updatedHtml = html.replace('</title>', `</title>\n\t${scriptTag}`);
return updatedHtml // 返回新的html字符串
}
}
}
export default addCDNsPlugin
首先,vite
插件是一个函数,它会返回一个对象,对象中必须要有一个name
属性
将其在vite.config.js
中应用起来:
import addCDNsPlugin from './vite-plugin-cdn.js'
export default defineConfig({
plugins: [
// 添加vue和Element Plus的cdn
addCDNsPlugin({
cdnUrls: [
"https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.37/vue.esm-browser.prod.min.js",
'//unpkg.com/element-plus'
]
})
]
})
- 构建:
pnpm build
- 预览构建后的页面:
pnpm preview
可以看到已成功加载了vue
,但也报错了
大概意思是:无法解析vue
模块
很明显,这种方式不对的,因为它是在全局注册一个Vue,让我们可以直接访问Vue
const { ref } = Vue
const num = ref(0)
所以如果使用这种方式,我们在main.ts
中就应该这么使用:
// main.ts
// 直接通过全局Vue对象导出而不是vue模块
/**
区别于: import { createApp } from 'vue'
*/
import { createApp } from Vue
import App from './App.vue'
createApp(App)
这很明显会导致在开发和生成的行为不一致,绝对不允许
2、启用import Map
如果使用ES
模块构建开发版本,那就可以像我们使用vue
库那样使用了
// 通过esm browser版本 + 原生ES模块
<script type="module">
import { ref, createApp } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
...
</script>
这种方式就很像
import { ref, createApp } from 'vue'
只需要将vue = "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
即可,其实就是给CDN
起一个别名
我们可以使用导入映射表来实现这个效果,即
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
其中,这个 导入映射表的内容必须是一个标准的JSON
此时就可以通过import { ref, createApp } from 'vue'
这种方式来访问vue了,这也是我们想要的结果
基于此,我们重写一下vite
插件
2.1 vite
插件 — 实现importmap
- 插件接收一个映射表
- 将其转化成标准的
JSON
- 拼接在
importmap
中,并添加到html中
function addImportmapPlugin(options) {
// importMap:映射表
// isAdd:控制是否添加(可以用来控制dev不添加,如果是发版则添加)
const { importMap, isAdd } = options
return {
name: 'vite-plugin-add-importmap',
transformIndexHtml(html: string) {
if (isAdd === false || !importMap) return html
// 这里采用JSON.stringify转化成标准的json
const cdnUrlStr = JSON.stringify(importMap)
// 构造importmap
const scriptTag =`
<script type="importmap">
{ "imports": ` +
cdnUrlStr +
` }
</script>`
const updatedHtml = html.replace('</title>', `</title>\n\t${scriptTag}`)
return updatedHtml
}
}
}
在vite
中应用起来:
VitePluginCDN({
// 传入importMap
importMap: {
"vue": "https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.37/vue.esm-browser.prod.min.js"
}
})
打包后的结果:
在网上找一个项目测试一下,成功加载出来了
到这一步,已验证这种方式是行得通的
如此便可以将vue
替换成cdn
,减少了vue
的体积
3、使用importmap
的方式真的合适吗
先来看一下importmap
的兼容性:
桌面端除了IE
浏览器不支持外,其它浏览器的兼容性还是不错的
移动端就稍微差一些
还要一个比较必要的条件,那就是:第三方包必须要支持浏览器端的ESM
标准
这一点其实限制了很多包,像vue就有,而ElementPlus就没有,并且大多数都是没有的
这就导致它的应用面其实不广泛
另外,虽然CDN
可以加速模块的加载速度,节约服务器资源,但是它也不无缺点:
- 可靠性:
CDN
依赖于第三方服务,如果CDN
服务器不稳定,那么会导致应用出问题 - 隐私性:请求第三方资源可能会暴露用户的一些信息
综上,为了节省一点资源,而冒这么大的风险,显然有点不太讨好
为此,最终还是选择本地打包vue
等第三方包,只不过把他们从业务模块中剥出来,打包成单独的chunk
当然,也不是说就完全放弃了这种方式,假以时日也许会考虑将其应用到生产上
目前将其上传到npm
仓库中,作为第一个版本,可以install
下来尝尝
后面会继续迭代,例如增加兼容性等
转载自:https://juejin.cn/post/7284261686608543800