vite-add-cdn-script 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -17,13 +17,16 @@ vite.config.ts
17
17
  ```typescript
18
18
  import { defineConfig } from "vite";
19
19
  import react from "@vitejs/plugin-react";
20
- import viteAddCdnScript from "../vite-add-cdn-script/lib/main";
20
+ import viteAddCdnScript from "vite-add-cdn-script";
21
21
  import externalGlobals from "rollup-plugin-external-globals";
22
22
 
23
- // 需要使用cdn
23
+ // 需要使用cdn库,按顺序添加,如react-router-dom需要依赖react、@remix-run/router、react-router,因此需要放在最后
24
24
  const externals = {
25
25
  react: "React",
26
26
  "react-dom": "ReactDOM",
27
+ "@remix-run/router": "@remix-run/router",
28
+ "react-router": "react-router",
29
+ "react-router-dom": "ReactRouterDOM",
27
30
  };
28
31
 
29
32
  export default defineConfig({
@@ -45,6 +48,7 @@ import react from "@vitejs/plugin-react";
45
48
  import viteAddCdnScript from "../vite-add-cdn-script/lib/main";
46
49
  import externalGlobals from "rollup-plugin-external-globals";
47
50
 
51
+ // 需要使用cdn的模块,会按顺序插入脚本,如
48
52
  const externals = {
49
53
  react: "React",
50
54
  "react-dom": "ReactDOM",
@@ -73,27 +77,13 @@ export default defineConfig({
73
77
 
74
78
  options
75
79
 
76
- | 参数 | 解析 | 类型 | 默认值 |
77
- | ------------ | ------------------- | --------------------------- | -------------------------------------------------------------------- |
78
- | protocol | 协议 | “http”\|“https” | https |
79
- | customScript | 自定义 cdn 脚本 | { [*key*: string]: string } | |
80
- | retryTimes | 重试次数 | number | 3 |
81
- | defaultCdns | 默认使用 cdn 的顺序 | string[] | ["bootcdn", "bytedance", "unpkg", "cdnjs", "jsdelivr", "staticfile"] |
80
+ | 参数 | 解析 | 类型 | 默认值 |
81
+ | ------------ | ------------------- | --------------------------- | --------------------- |
82
+ | customScript | 自定义 cdn 脚本 | { [*key*: string]: string } | |
83
+ | retryTimes | 重试次数 | number | 3 |
84
+ | defaultCdns | 默认使用 cdn 的顺序 | string[] | ["jsdelivr", "unpkg"] |
82
85
 
83
86
  ## 注意事项
84
87
 
85
- 因为 cdn 对包管理的命名有很大的不同,默认是使用了 dist/xxx.min.js 的文件,如果您使用的库的 cdn 文件不是这个的话,则需要配置为自定义的 cdn
86
-
87
- 目前做了适配的非 xxx.min.js 适配的库如下,如果你有合适的 cdn 源或者,需要适配的库,欢迎提交 issue 或者 pr!!!
88
-
89
- ```
90
- {
91
- react: "umd/react.production.min.js",
92
- "react-dom": "umd/react-dom.production.min.js",
93
- "react-router-dom": "react-router-dom.production.min.js",
94
- mobx: "dist/mobx.umd.production.min.js",
95
- "mobx-react": "/dist/mobxreact.umd.production.min.js",
96
- vue: "/dist/vue.global.prod.js",
97
- "vue-router": "/dist/vue-router.global.prod.js",
98
- }
99
- ```
88
+ - 接入了各大 cdn api 接口进行请求,默认会保存一份 cdn 的缓存在你的根目录中`.cdn-cache.json`。如果发现缓存的资源有问题可以删除该文件,然后重新执行打包流程。
89
+ - 按顺序添加cdn,如react-router-dom需要依赖react、@remix-run/router、react-router,因此需要放在最后。
package/dist/index.js CHANGED
@@ -1,90 +1,344 @@
1
- import v from "node:path";
2
- import h from "node:fs";
3
- const u = {
4
- jsdelivr: "cdn.jsdelivr.net/npm",
5
- unpkg: "unpkg.com"
6
- }, m = {
7
- jsdelivr: "@",
8
- unpkg: "@"
9
- }, x = {
10
- // react
11
- react: "umd/react.production.min.js",
12
- "react-dom": "umd/react-dom.production.min.js",
13
- "@remix-run/router": "dist/router.umd.min.js",
14
- "react-router": "dist/umd/react-router.production.min.js",
15
- "react-router-dom": "dist/umd/react-router-dom.production.min.js",
16
- mobx: "dist/mobx.umd.production.min.js",
17
- "mobx-react": "dist/mobxreact.umd.production.min.js",
18
- // vue
19
- vue: "dist/vue.global.min.js",
20
- "vue-router": "dist/vue-router.global.min.js",
21
- // tool
22
- dayjs: "dayjs.min.js",
23
- moment: "moment.min.js",
24
- lodash: "lodash.min.js"
1
+ var D = Object.defineProperty;
2
+ var F = (n, t, e) => t in n ? D(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
3
+ var y = (n, t, e) => (F(n, typeof t != "symbol" ? t + "" : t, e), e);
4
+ import U from "node:path";
5
+ import w from "node:fs";
6
+ import b from "node:https";
7
+ class E {
8
+ constructor() {
9
+ y(this, "cdnCache", {});
10
+ y(this, "cdnCachePath", "");
11
+ this.cdnCachePath = U.resolve(process.cwd(), "./.cdn-cache.json");
12
+ }
13
+ // 初始化cdn缓存
14
+ async init() {
15
+ try {
16
+ const t = await w.readFileSync(this.cdnCachePath, "utf-8");
17
+ this.cdnCache = JSON.parse(t);
18
+ } catch {
19
+ console.log("cdn缓存文件不存在,创建缓存文件"), this.cdnCache = {}, await w.writeFileSync(this.cdnCachePath, "", "utf-8");
20
+ }
21
+ }
22
+ /**
23
+ * 获取cdn缓存
24
+ * @param packageName 包名
25
+ * @param version 版本
26
+ */
27
+ getCdnCache(t, e) {
28
+ var r;
29
+ return (r = this.cdnCache[t]) == null ? void 0 : r[e];
30
+ }
31
+ /**
32
+ * 设置cdn缓存
33
+ * @param packageName 包名
34
+ * @param version 版本
35
+ * @param urls 地址列表
36
+ */
37
+ setCdnCache(t, e, r) {
38
+ this.cdnCache[t] ? this.cdnCache[t][e] = r : this.cdnCache[t] = {
39
+ [e]: r
40
+ };
41
+ }
42
+ /**
43
+ * 更新cdn缓存
44
+ */
45
+ async save() {
46
+ await w.writeFileSync(this.cdnCachePath, JSON.stringify(this.cdnCache), "utf-8");
47
+ }
48
+ }
49
+ let $;
50
+ const L = async () => ($ || ($ = new E(), await $.init()), $), f = {
51
+ //get请求封装
52
+ get: (n, t, e) => new Promise((r, s) => {
53
+ try {
54
+ b.get(n, (i) => {
55
+ let o = "";
56
+ i.on("data", (c) => {
57
+ o += c;
58
+ }), i.on("end", () => {
59
+ t == null || t(o), r(o);
60
+ });
61
+ });
62
+ } catch (i) {
63
+ e == null || e(i), s(i);
64
+ }
65
+ })
66
+ }, N = async (n) => {
67
+ const t = /^(https:\/\/.*\d+\.\d+\.\d+\/).+?\.js$/;
68
+ if (t.test(n)) {
69
+ const e = n.replace(t, (r, s) => `${s}package.json`);
70
+ return JSON.parse(await f.get(e));
71
+ } else
72
+ throw new Error(`${n} 不是正确的url`);
73
+ }, I = (n, t) => {
74
+ var e, r;
75
+ return ((e = n.dependencies) == null ? void 0 : e[t]) || ((r = n.devDependencies) == null ? void 0 : r[t]);
76
+ }, k = (n) => n.reduce((t, e) => (e.type === "file" ? t.push({ name: e.path }) : e.files && t.push(...k(e.files)), t), []), x = {
77
+ jsdelivr: {
78
+ getFileList: (n, t) => new Promise((e, r) => {
79
+ f.get(
80
+ `https://data.jsdelivr.com/v1/stats/packages/npm/${n}@${t}/files`,
81
+ (s) => {
82
+ const i = JSON.parse(s);
83
+ if (i.length === 0) {
84
+ r(new Error(`${n}@${t} not found`));
85
+ return;
86
+ }
87
+ e({ fileList: i });
88
+ },
89
+ (s) => {
90
+ r(s);
91
+ }
92
+ );
93
+ }),
94
+ // 拼接url
95
+ getUrl: (n, t, e) => `https://cdn.jsdelivr.net/npm/${n}@${t}${e}`
96
+ },
97
+ unpkg: {
98
+ getFileList: (n, t) => new Promise((e, r) => {
99
+ f.get(
100
+ `https://unpkg.com/${n}@${t}/?meta`,
101
+ (s) => {
102
+ const i = JSON.parse(s);
103
+ e({ fileList: k(i.files || []) });
104
+ },
105
+ (s) => {
106
+ r(s);
107
+ }
108
+ );
109
+ }),
110
+ getUrl: (n, t, e) => `https://unpkg.com/${n}@${t}${e}`
111
+ },
112
+ bootcdn: {
113
+ getFileList: (n, t) => new Promise((e, r) => {
114
+ f.get(
115
+ `https://api.bootcdn.cn/libraries/${n}`,
116
+ (s) => {
117
+ const i = JSON.parse(s);
118
+ if (i.length === 0) {
119
+ r(new Error(`${n} not found in bootcdn`));
120
+ return;
121
+ }
122
+ const o = i[0], a = o.assets.find((d) => d.version === t);
123
+ if (!a) {
124
+ r(new Error(`${n}@${t} not found in ${o.name}`));
125
+ return;
126
+ }
127
+ const l = a.files.map((d) => ({
128
+ name: "/" + d
129
+ }));
130
+ e({ fileList: l, recommendFileName: o.filename });
131
+ },
132
+ (s) => {
133
+ r(s);
134
+ }
135
+ );
136
+ }),
137
+ getUrl: (n, t, e) => `https://cdn.bootcdn.net/ajax/libs/${n}/${t}${e}`
138
+ },
139
+ cdnjs: {
140
+ getFileList: (n, t) => new Promise((e, r) => {
141
+ f.get(
142
+ `https://api.cdnjs.com/libraries/${n}/${t}`,
143
+ (s) => {
144
+ const i = JSON.parse(s);
145
+ if (i.error) {
146
+ r(new Error(`cdnjs: ${n}@${t} not found`));
147
+ return;
148
+ }
149
+ e({
150
+ fileList: i.rawFiles.map((o) => ({
151
+ name: "/" + o
152
+ }))
153
+ });
154
+ },
155
+ (s) => {
156
+ r(s);
157
+ }
158
+ );
159
+ }),
160
+ getUrl: (n, t, e) => `https://cdnjs.cloudflare.com/ajax/libs/${n}/${t}${e}`
161
+ }
162
+ }, R = async (n, t, e) => {
163
+ var o;
164
+ const r = (o = t.match(/\d+(.\d+)?(.\d+)?/)) == null ? void 0 : o[0];
165
+ if (!r)
166
+ throw new Error(`${n} version ${t} is not valid`);
167
+ const s = await x[e].getFileList(n, r).catch((c) => {
168
+ throw c;
169
+ }), i = A(s, n);
170
+ if (!i)
171
+ throw new Error(`在 ${e} 中找不到 ${n}@${r} 文件,请检查包名或版本号`);
172
+ return x[e].getUrl(n, r, i);
173
+ }, A = ({ fileList: n }, t) => {
174
+ var i, o;
175
+ let e = [
176
+ `umd/${t}.production.min.js`,
177
+ /umd\/.+?\.production\.min\.js$/,
178
+ /dist\/.+?\.production\.min\.js$/,
179
+ /dist\/.+?\.umd\.min\.js$/,
180
+ `dist/${t}.prod.min.js`,
181
+ /dist\/.+?\.global.prod.min.js/,
182
+ `dist/${t}.min.js`,
183
+ /.+?\.global.prod.min.js/,
184
+ /.+?.global.prod.js/,
185
+ /lib\/.+?\.min\.js$/,
186
+ /dist\/.+?\.min\.js$/,
187
+ /index\.min\.js$/,
188
+ /index\.js$/,
189
+ /\.min\.js$/,
190
+ /\.js$/
191
+ ];
192
+ const r = ["runtime", "compiler", ".esm", ".cjs", "development"].filter((c) => !t.includes(c));
193
+ let s = "";
194
+ for (let c of e)
195
+ if (c instanceof RegExp ? s = ((i = n.find((a) => c.test(a.name) && !r.some((l) => a.name.includes(l)))) == null ? void 0 : i.name) || "" : s = ((o = n.find((a) => a.name.includes(c) && !r.some((l) => a.name.includes(l)))) == null ? void 0 : o.name) || "", s)
196
+ break;
197
+ return s;
25
198
  };
26
- function D(p) {
27
- const {
28
- protocol: l = "https",
29
- customScript: o = {},
30
- retryTimes: f = 1,
31
- defaultCdns: s = ["jsdelivr", "unpkg"],
32
- customFilepath: j = {}
33
- } = p;
34
- let a;
35
- const c = { ...x, ...j };
199
+ function M(n, t) {
200
+ const e = n.replace(/^\D/, "").split("."), r = t.replace(/^\D/, "").split("."), s = Math.max(e.length, r.length);
201
+ for (; e.length < s; )
202
+ e.push("0");
203
+ for (; r.length < s; )
204
+ r.push("0");
205
+ for (let i = 0; i < s; i++) {
206
+ const o = parseInt(e[i], 10), c = parseInt(r[i], 10);
207
+ if (o > c)
208
+ return 1;
209
+ if (o < c)
210
+ return -1;
211
+ }
212
+ return 0;
213
+ }
214
+ function V(n, t) {
215
+ for (let e in t)
216
+ Object.prototype.hasOwnProperty.call(t, e) && (n[e] ? M(n[e], t[e]) === -1 && (n[e] = t[e]) : n[e] = t[e]);
217
+ return n;
218
+ }
219
+ async function S({ external: n, packageData: t, customScript: e, defaultCdns: r }) {
220
+ let s = [], i = !1;
221
+ const o = await L();
222
+ return await Promise.all(
223
+ n.map(async (c) => {
224
+ const a = I(t, c);
225
+ if (e[c])
226
+ return {
227
+ urls: [],
228
+ key: c
229
+ };
230
+ if (!a) {
231
+ s.push(c);
232
+ return;
233
+ }
234
+ const l = o.getCdnCache(c, a);
235
+ if (l)
236
+ return {
237
+ urls: l,
238
+ key: c
239
+ };
240
+ {
241
+ i = !0;
242
+ const d = {
243
+ urls: await Promise.all(
244
+ r.map(async (h) => await R(c, a, h))
245
+ ),
246
+ key: c
247
+ };
248
+ return o.setCdnCache(c, a, d.urls), d;
249
+ }
250
+ })
251
+ ).then((c) => (i && o.save(), {
252
+ urls: c,
253
+ noVersionPackages: s
254
+ }));
255
+ }
256
+ function q(n) {
257
+ const { customScript: t = {}, retryTimes: e = 1, defaultCdns: r = ["jsdelivr", "unpkg"] } = n;
258
+ let s;
36
259
  return {
37
260
  name: "vite-add-cdn-script",
38
261
  enforce: "pre",
39
262
  apply: "build",
40
- config(e) {
41
- a = e;
263
+ config(i) {
264
+ s = i;
42
265
  },
43
- transformIndexHtml(e) {
44
- const g = v.resolve(process.cwd(), "package.json");
266
+ async transformIndexHtml(i) {
267
+ if (!r || r.length === 0)
268
+ throw new Error("defaultCdns不能为空");
269
+ const o = U.resolve(process.cwd(), "package.json");
45
270
  try {
46
- const r = s[0], b = h.readFileSync(g, "utf-8"), i = JSON.parse(b), O = a.build.rollupOptions.external;
47
- let n = "" + `<script>
48
- const separators = JSON.parse('${JSON.stringify(m)}');
49
- const cdnUrlObj = JSON.parse('${JSON.stringify(u)}');
50
- const defaultCdns = JSON.parse('${JSON.stringify(s)}');
51
- function errorCDN(e) {
52
- const nextCur = parseInt(e.getAttribute("data-cur")) + 1;
53
- if(nextCur>=${f}){return;}
54
- const filename = e.getAttribute("data-filename");
55
- const key = e.getAttribute("data-key");
56
- const urlName = defaultCdns[nextCur]
57
- // 组装新的cdn链接
58
- const url = location.protocol + "//" + cdnUrlObj[urlName] + "/" + key + separators[urlName] + filename;
59
- // 克隆原标签
60
- const tagName = e.tagName
61
- const cdnDOM = document.createElement(tagName);
62
- cdnDOM.setAttribute(tagName === 'SCRIPT' ?'src' : 'href', url);
63
- Object.keys(e.dataset).forEach(_key => {
64
- cdnDOM.setAttribute('data-'+_key, e.dataset[_key]);
65
- })
66
- cdnDOM.setAttribute("data-cur", nextCur.toString());
67
- cdnDOM.setAttribute("onerror", "errorCDN(this)");
68
- document.head.appendChild(cdnDOM);
69
- e.remove();
70
- }
71
- <\/script>`;
72
- return O.forEach((t) => {
73
- if (o[t])
74
- n += o[t];
271
+ const c = w.readFileSync(o, "utf-8"), a = JSON.parse(c), l = s.build.rollupOptions.external, d = {};
272
+ let h = "";
273
+ const { urls: C, noVersionPackages: P } = await S({
274
+ external: l,
275
+ packageData: a,
276
+ customScript: t,
277
+ defaultCdns: r
278
+ });
279
+ if (P.length > 0) {
280
+ const p = { dependencies: {} };
281
+ await Promise.all(
282
+ C.map(async (g) => {
283
+ if (!g)
284
+ return;
285
+ const { key: v, urls: O } = g, j = t[v] || O[0];
286
+ if (!j)
287
+ return;
288
+ const J = await N(j);
289
+ V(p.dependencies, J.dependencies);
290
+ })
291
+ );
292
+ const { urls: m, noVersionPackages: u } = await S({
293
+ external: P,
294
+ packageData: p,
295
+ customScript: t,
296
+ defaultCdns: r
297
+ });
298
+ if (C.push(...m), u.length > 0)
299
+ throw console.error(`找不到${u.join(",")}的版本`), new Error(`找不到${u.join(",")}的版本`);
300
+ }
301
+ return C.forEach((p) => {
302
+ if (!p)
303
+ return;
304
+ const { urls: m, key: u } = p;
305
+ if (t[u])
306
+ h += t[u];
75
307
  else {
76
- const d = (i.dependencies[t] ? m[r] + i.dependencies[t].replace("^", "") : "") + "/" + (c[t] ? c[t] : `dist/${t}.min.js`), N = `${l}://${u[r]}/${t}${d}`;
77
- n += `<script src="${N}" type="text/javascript" onerror="errorCDN(this)" data-cur="0" data-key="${t}" data-filename="${d}"><\/script>
308
+ d[u] = m;
309
+ const g = m[0];
310
+ h += `<script src="${g}" type="text/javascript" crossorigin="anonymous" onerror="errorCDN(this)" data-cur="0" data-key="${u}"><\/script>
78
311
  `;
79
312
  }
80
- }), e = e.replace("</head>", `${n}</head>`), e;
81
- } catch (r) {
82
- console.error("获取dependencies出错:", r);
313
+ }), h = `<script>
314
+ function errorCDN(e) {
315
+ const packNameUrl = JSON.parse('${JSON.stringify(d)}');
316
+ const nextCur = parseInt(e.getAttribute("data-cur")) + 1;
317
+ if(nextCur>${e}){return;}
318
+
319
+ const key = e.getAttribute("data-key");
320
+ if(nextCur>=packNameUrl[key].length){return;}
321
+ // 新的cdn链接
322
+ const url = packNameUrl[key][nextCur]
323
+ // 克隆原标签
324
+ const tagName = e.tagName
325
+ const cdnDOM = document.createElement(tagName);
326
+ cdnDOM.setAttribute(tagName === 'SCRIPT' ?'src' : 'href', url);
327
+ Object.keys(e.dataset).forEach(_key => {
328
+ cdnDOM.setAttribute('data-'+_key, e.dataset[_key]);
329
+ })
330
+ cdnDOM.setAttribute("data-cur", nextCur.toString());
331
+ cdnDOM.setAttribute("onerror", "errorCDN(this)");
332
+ document.head.appendChild(cdnDOM);
333
+ e.remove();
334
+ }
335
+ <\/script>` + h, i = i.replace("</head>", `${h}</head>`), i;
336
+ } catch (c) {
337
+ console.error("获取dependencies出错:", c);
83
338
  }
84
339
  }
85
340
  };
86
341
  }
87
342
  export {
88
- D as default,
89
- x as npmProObj
343
+ q as default
90
344
  };
@@ -1,26 +1,24 @@
1
- (function(e,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("node:path"),require("node:fs")):typeof define=="function"&&define.amd?define(["exports","node:path","node:fs"],r):(e=typeof globalThis<"u"?globalThis:e||self,r(e.index={},e.path,e.fs))})(this,function(e,r,j){"use strict";const c={jsdelivr:"cdn.jsdelivr.net/npm",unpkg:"unpkg.com"},a={jsdelivr:"@",unpkg:"@"},i={react:"umd/react.production.min.js","react-dom":"umd/react-dom.production.min.js","@remix-run/router":"dist/router.umd.min.js","react-router":"dist/umd/react-router.production.min.js","react-router-dom":"dist/umd/react-router-dom.production.min.js",mobx:"dist/mobx.umd.production.min.js","mobx-react":"dist/mobxreact.umd.production.min.js",vue:"dist/vue.global.min.js","vue-router":"dist/vue-router.global.min.js",dayjs:"dayjs.min.js",moment:"moment.min.js",lodash:"lodash.min.js"};function b(g){const{protocol:O="https",customScript:d={},retryTimes:h=1,defaultCdns:u=["jsdelivr","unpkg"],customFilepath:N={}}=g;let p;const m={...i,...N};return{name:"vite-add-cdn-script",enforce:"pre",apply:"build",config(n){p=n},transformIndexHtml(n){const v=r.resolve(process.cwd(),"package.json");try{const o=u[0],S=j.readFileSync(v,"utf-8"),l=JSON.parse(S),x=p.build.rollupOptions.external;let s=""+`<script>
2
- const separators = JSON.parse('${JSON.stringify(a)}');
3
- const cdnUrlObj = JSON.parse('${JSON.stringify(c)}');
4
- const defaultCdns = JSON.parse('${JSON.stringify(u)}');
5
- function errorCDN(e) {
6
- const nextCur = parseInt(e.getAttribute("data-cur")) + 1;
7
- if(nextCur>=${h}){return;}
8
- const filename = e.getAttribute("data-filename");
9
- const key = e.getAttribute("data-key");
10
- const urlName = defaultCdns[nextCur]
11
- // 组装新的cdn链接
12
- const url = location.protocol + "//" + cdnUrlObj[urlName] + "/" + key + separators[urlName] + filename;
13
- // 克隆原标签
14
- const tagName = e.tagName
15
- const cdnDOM = document.createElement(tagName);
16
- cdnDOM.setAttribute(tagName === 'SCRIPT' ?'src' : 'href', url);
17
- Object.keys(e.dataset).forEach(_key => {
18
- cdnDOM.setAttribute('data-'+_key, e.dataset[_key]);
19
- })
20
- cdnDOM.setAttribute("data-cur", nextCur.toString());
21
- cdnDOM.setAttribute("onerror", "errorCDN(this)");
22
- document.head.appendChild(cdnDOM);
23
- e.remove();
24
- }
25
- <\/script>`;return x.forEach(t=>{if(d[t])s+=d[t];else{const f=(l.dependencies[t]?a[o]+l.dependencies[t].replace("^",""):"")+"/"+(m[t]?m[t]:`dist/${t}.min.js`),y=`${O}://${c[o]}/${t}${f}`;s+=`<script src="${y}" type="text/javascript" onerror="errorCDN(this)" data-cur="0" data-key="${t}" data-filename="${f}"><\/script>
26
- `}}),n=n.replace("</head>",`${s}</head>`),n}catch(o){console.error("获取dependencies出错:",o)}}}}e.default=b,e.npmProObj=i,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(d,a){typeof exports=="object"&&typeof module<"u"?module.exports=a(require("node:path"),require("node:fs"),require("node:https")):typeof define=="function"&&define.amd?define(["node:path","node:fs","node:https"],a):(d=typeof globalThis<"u"?globalThis:d||self,d.index=a(d.path,d.fs,d.https))})(this,function(d,a,f){"use strict";var V=Object.defineProperty;var T=(d,a,f)=>a in d?V(d,a,{enumerable:!0,configurable:!0,writable:!0,value:f}):d[a]=f;var P=(d,a,f)=>(T(d,typeof a!="symbol"?a+"":a,f),f);class O{constructor(){P(this,"cdnCache",{});P(this,"cdnCachePath","");this.cdnCachePath=d.resolve(process.cwd(),"./.cdn-cache.json")}async init(){try{const e=await a.readFileSync(this.cdnCachePath,"utf-8");this.cdnCache=JSON.parse(e)}catch{console.log("cdn缓存文件不存在,创建缓存文件"),this.cdnCache={},await a.writeFileSync(this.cdnCachePath,"","utf-8")}}getCdnCache(e,t){var r;return(r=this.cdnCache[e])==null?void 0:r[t]}setCdnCache(e,t,r){this.cdnCache[e]?this.cdnCache[e][t]=r:this.cdnCache[e]={[t]:r}}async save(){await a.writeFileSync(this.cdnCachePath,JSON.stringify(this.cdnCache),"utf-8")}}let w;const J=async()=>(w||(w=new O,await w.init()),w),g={get:(n,e,t)=>new Promise((r,s)=>{try{f.get(n,i=>{let o="";i.on("data",c=>{o+=c}),i.on("end",()=>{e==null||e(o),r(o)})})}catch(i){t==null||t(i),s(i)}})},D=async n=>{const e=/^(https:\/\/.*\d+\.\d+\.\d+\/).+?\.js$/;if(e.test(n)){const t=n.replace(e,(r,s)=>`${s}package.json`);return JSON.parse(await g.get(t))}else throw new Error(`${n} 不是正确的url`)},F=(n,e)=>{var t,r;return((t=n.dependencies)==null?void 0:t[e])||((r=n.devDependencies)==null?void 0:r[e])},x=n=>n.reduce((e,t)=>(t.type==="file"?e.push({name:t.path}):t.files&&e.push(...x(t.files)),e),[]),S={jsdelivr:{getFileList:(n,e)=>new Promise((t,r)=>{g.get(`https://data.jsdelivr.com/v1/stats/packages/npm/${n}@${e}/files`,s=>{const i=JSON.parse(s);if(i.length===0){r(new Error(`${n}@${e} not found`));return}t({fileList:i})},s=>{r(s)})}),getUrl:(n,e,t)=>`https://cdn.jsdelivr.net/npm/${n}@${e}${t}`},unpkg:{getFileList:(n,e)=>new Promise((t,r)=>{g.get(`https://unpkg.com/${n}@${e}/?meta`,s=>{const i=JSON.parse(s);t({fileList:x(i.files||[])})},s=>{r(s)})}),getUrl:(n,e,t)=>`https://unpkg.com/${n}@${e}${t}`},bootcdn:{getFileList:(n,e)=>new Promise((t,r)=>{g.get(`https://api.bootcdn.cn/libraries/${n}`,s=>{const i=JSON.parse(s);if(i.length===0){r(new Error(`${n} not found in bootcdn`));return}const o=i[0],u=o.assets.find(h=>h.version===e);if(!u){r(new Error(`${n}@${e} not found in ${o.name}`));return}const l=u.files.map(h=>({name:"/"+h}));t({fileList:l,recommendFileName:o.filename})},s=>{r(s)})}),getUrl:(n,e,t)=>`https://cdn.bootcdn.net/ajax/libs/${n}/${e}${t}`},cdnjs:{getFileList:(n,e)=>new Promise((t,r)=>{g.get(`https://api.cdnjs.com/libraries/${n}/${e}`,s=>{const i=JSON.parse(s);if(i.error){r(new Error(`cdnjs: ${n}@${e} not found`));return}t({fileList:i.rawFiles.map(o=>({name:"/"+o}))})},s=>{r(s)})}),getUrl:(n,e,t)=>`https://cdnjs.cloudflare.com/ajax/libs/${n}/${e}${t}`}},E=async(n,e,t)=>{var o;const r=(o=e.match(/\d+(.\d+)?(.\d+)?/))==null?void 0:o[0];if(!r)throw new Error(`${n} version ${e} is not valid`);const s=await S[t].getFileList(n,r).catch(c=>{throw c}),i=L(s,n);if(!i)throw new Error(`在 ${t} 中找不到 ${n}@${r} 文件,请检查包名或版本号`);return S[t].getUrl(n,r,i)},L=({fileList:n},e)=>{var i,o;let t=[`umd/${e}.production.min.js`,/umd\/.+?\.production\.min\.js$/,/dist\/.+?\.production\.min\.js$/,/dist\/.+?\.umd\.min\.js$/,`dist/${e}.prod.min.js`,/dist\/.+?\.global.prod.min.js/,`dist/${e}.min.js`,/.+?\.global.prod.min.js/,/.+?.global.prod.js/,/lib\/.+?\.min\.js$/,/dist\/.+?\.min\.js$/,/index\.min\.js$/,/index\.js$/,/\.min\.js$/,/\.js$/];const r=["runtime","compiler",".esm",".cjs","development"].filter(c=>!e.includes(c));let s="";for(let c of t)if(c instanceof RegExp?s=((i=n.find(u=>c.test(u.name)&&!r.some(l=>u.name.includes(l))))==null?void 0:i.name)||"":s=((o=n.find(u=>u.name.includes(c)&&!r.some(l=>u.name.includes(l))))==null?void 0:o.name)||"",s)break;return s};function b(n,e){const t=n.replace(/^\D/,"").split("."),r=e.replace(/^\D/,"").split("."),s=Math.max(t.length,r.length);for(;t.length<s;)t.push("0");for(;r.length<s;)r.push("0");for(let i=0;i<s;i++){const o=parseInt(t[i],10),c=parseInt(r[i],10);if(o>c)return 1;if(o<c)return-1}return 0}function N(n,e){for(let t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]?b(n[t],e[t])===-1&&(n[t]=e[t]):n[t]=e[t]);return n}async function U({external:n,packageData:e,customScript:t,defaultCdns:r}){let s=[],i=!1;const o=await J();return await Promise.all(n.map(async c=>{const u=F(e,c);if(t[c])return{urls:[],key:c};if(!u){s.push(c);return}const l=o.getCdnCache(c,u);if(l)return{urls:l,key:c};{i=!0;const h={urls:await Promise.all(r.map(async m=>await E(c,u,m))),key:c};return o.setCdnCache(c,u,h.urls),h}})).then(c=>(i&&o.save(),{urls:c,noVersionPackages:s}))}function I(n){const{customScript:e={},retryTimes:t=1,defaultCdns:r=["jsdelivr","unpkg"]}=n;let s;return{name:"vite-add-cdn-script",enforce:"pre",apply:"build",config(i){s=i},async transformIndexHtml(i){if(!r||r.length===0)throw new Error("defaultCdns不能为空");const o=d.resolve(process.cwd(),"package.json");try{const c=a.readFileSync(o,"utf-8"),u=JSON.parse(c),l=s.build.rollupOptions.external,h={};let m="";const{urls:j,noVersionPackages:k}=await U({external:l,packageData:u,customScript:e,defaultCdns:r});if(k.length>0){const $={dependencies:{}};await Promise.all(j.map(async y=>{if(!y)return;const{key:R,urls:A}=y,v=e[R]||A[0];if(!v)return;const M=await D(v);N($.dependencies,M.dependencies)}));const{urls:C,noVersionPackages:p}=await U({external:k,packageData:$,customScript:e,defaultCdns:r});if(j.push(...C),p.length>0)throw console.error(`找不到${p.join(",")}的版本`),new Error(`找不到${p.join(",")}的版本`)}return j.forEach($=>{if(!$)return;const{urls:C,key:p}=$;if(e[p])m+=e[p];else{h[p]=C;const y=C[0];m+=`<script src="${y}" type="text/javascript" crossorigin="anonymous" onerror="errorCDN(this)" data-cur="0" data-key="${p}"><\/script>
2
+ `}}),m=`<script>
3
+ function errorCDN(e) {
4
+ const packNameUrl = JSON.parse('${JSON.stringify(h)}');
5
+ const nextCur = parseInt(e.getAttribute("data-cur")) + 1;
6
+ if(nextCur>${t}){return;}
7
+
8
+ const key = e.getAttribute("data-key");
9
+ if(nextCur>=packNameUrl[key].length){return;}
10
+ // 新的cdn链接
11
+ const url = packNameUrl[key][nextCur]
12
+ // 克隆原标签
13
+ const tagName = e.tagName
14
+ const cdnDOM = document.createElement(tagName);
15
+ cdnDOM.setAttribute(tagName === 'SCRIPT' ?'src' : 'href', url);
16
+ Object.keys(e.dataset).forEach(_key => {
17
+ cdnDOM.setAttribute('data-'+_key, e.dataset[_key]);
18
+ })
19
+ cdnDOM.setAttribute("data-cur", nextCur.toString());
20
+ cdnDOM.setAttribute("onerror", "errorCDN(this)");
21
+ document.head.appendChild(cdnDOM);
22
+ e.remove();
23
+ }
24
+ <\/script>`+m,i=i.replace("</head>",`${m}</head>`),i}catch(c){console.error("获取dependencies出错:",c)}}}}return I});
package/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  interface IOptions {
2
- protocol?: string;
3
2
  customScript?: { [key: string]: string };
4
- customFilepath?: { [key: string]: string };
5
3
  retryTimes?: number;
6
4
  defaultCdns?: string[];
7
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-add-cdn-script",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "keywords": [
5
5
  "vite",
6
6
  "cdn",
@@ -30,20 +30,30 @@
30
30
  },
31
31
  "scripts": {
32
32
  "dev": "vite",
33
- "build": "tsc && vite build"
33
+ "build:page": "tsc && cross-env BUILD_MODE=page vite build",
34
+ "build": "tsc && cross-env BUILD_MODE=lib vite build",
35
+ "test": "jest"
34
36
  },
35
37
  "devDependencies": {
38
+ "@jest/globals": "^29.7.0",
36
39
  "@types/node": "^20.12.11",
37
40
  "@types/react": "^18.3.2",
38
41
  "@types/react-dom": "^18.3.0",
39
42
  "@vitejs/plugin-react": "^4.2.1",
43
+ "cross-env": "^7.0.3",
44
+ "jest": "^29.7.0",
40
45
  "react": "^18.3.1",
41
46
  "react-dom": "^18.3.1",
47
+ "react-router-dom": "^6.23.1",
48
+ "rollup-plugin-external-globals": "^0.10.0",
42
49
  "rollup-plugin-node-externals": "^7.1.2",
50
+ "ts-jest": "^29.1.4",
51
+ "ts-node": "^10.9.2",
43
52
  "typescript": "^5.4.5",
44
53
  "vite": "^5.2.11"
45
54
  },
46
55
  "publishConfig": {
47
56
  "registry": "https://registry.npmjs.org/"
48
- }
49
- }
57
+ },
58
+ "dependencies": {}
59
+ }