Skip to content

Instantly share code, notes, and snippets.

@yyx990803
Created January 13, 2021 02:58
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yyx990803/341b4bfc84086ad092e619ee70ee1d8d to your computer and use it in GitHub Desktop.
Save yyx990803/341b4bfc84086ad092e619ee70ee1d8d to your computer and use it in GitHub Desktop.
A vite plugin that loads the specified deps over CDN during dev, and downloads/includes them into bundle during build.
// example vite.config.js
import { cdn } from './vite-plugin-cdn'
export default {
plugins: [
// also supported: esm.run, jspm
// loads the dep over the CDN during dev
// auto downloads and includes into the bundle during build
cdn('skypack', {
vue: '^3.0.5'
})
]
}
const fetch = require('node-fetch')
const { init, parse } = require('es-module-lexer')
const MagicString = require('magic-string').default
const mapping = {
skypack: ['https://cdn.skypack.dev/'],
'esm.run': ['https://cdn.jsdelivr.net/', 'npm/', '/+esm'],
jspm: ['https://jspm.dev/']
}
export function cdn(provider, include = {}) {
if (!(provider in mapping)) {
throw new Error(
`Unsupported provider: ${provider}. Supported providers: ${Object.keys(
mapping
).join(', ')}`
)
}
const [base, prefix = '', postfix = ''] = mapping[provider]
let isBuild = false
let needSourcemap = true
return {
name: 'cdn-bundle',
enforce: 'pre',
config() {
return {
alias: Object.keys(include).map((id) => ({
find: id,
replacement: `${base}${prefix}${id}@${include[id]}${postfix}`
}))
}
},
configResolved(config) {
isBuild = config.command === 'build'
needSourcemap = isBuild ? config.build.sourcemap : true
},
resolveId(id) {
if (id.startsWith(base)) {
return id
}
},
async load(id) {
if (isBuild && id.startsWith(base)) {
const src = await (await fetch(id)).text()
await init
let imports
try {
imports = parse(src)[0]
} catch (e) {
const err = new Error(`failed to parse external request ${id}.`)
this.error(err, e.idx)
}
if (!imports.length) {
return src
}
let s
const ss = () => s || (s = new MagicString(src))
for (let index = 0; index < imports.length; index++) {
const { s: start, e: end, d: dynamicIndex } = imports[index]
let url = src.slice(start, end)
let isLiteralDynamic = false
if (dynamicIndex >= 0) {
url = url.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '').trim()
const literalIdMatch = url.match(/^'([^']+)'|"([^"]+)"$/)
if (literalIdMatch) {
isLiteralDynamic = true
url = literalIdMatch[1] || literalIdMatch[2]
}
}
if (url.startsWith('/')) {
url = base + url.slice(1)
ss().overwrite(start, end, isLiteralDynamic ? `"${url}"` : url)
}
}
if (s) {
return {
code: s.toString(),
map: needSourcemap ? s.generateMap({ hiref: true }) : null
}
} else {
return src
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment