UNPKG

5.5 kBJavaScriptView Raw
1import cacache from 'cacache';
2import * as colors from 'kleur/colors';
3import PQueue from 'p-queue';
4import validatePackageName from 'validate-npm-package-name';
5import { fetchCDNResource, PIKA_CDN, RESOURCE_CACHE } from './util.js';
6/**
7 * Given an install specifier, attempt to resolve it from the CDN.
8 * If no lockfile exists or if the entry is not found in the lockfile, attempt to resolve
9 * it from the CDN directly. Otherwise, use the URL found in the lockfile and attempt to
10 * check the local cache first.
11 *
12 * All resolved URLs are populated into the local cache, where our internal Rollup engine
13 * will load them from when it installs your dependencies to disk.
14 */
15async function resolveDependency(installSpecifier, packageSemver, lockfile, canRetry = true) {
16 // Right now, the CDN is only for top-level JS packages. The CDN doesn't support CSS,
17 // non-JS assets, and has limited support for deep package imports. Snowpack
18 // will automatically fall-back any failed/not-found assets from local
19 // node_modules/ instead.
20 if (!validatePackageName(installSpecifier).validForNewPackages) {
21 return null;
22 }
23 // Grab the installUrl from our lockfile if it exists, otherwise resolve it yourself.
24 let installUrl;
25 let installUrlType;
26 if (lockfile && lockfile.imports[installSpecifier]) {
27 installUrl = lockfile.imports[installSpecifier];
28 installUrlType = 'pin';
29 }
30 else {
31 if (packageSemver === 'latest') {
32 console.warn(`warn(${installSpecifier}): Not found in "dependencies". Using latest package version...`);
33 }
34 if (packageSemver.startsWith('npm:@reactesm') || packageSemver.startsWith('npm:@pika/react')) {
35 throw new Error(`React workaround packages no longer needed! Revert to the official React & React-DOM packages.`);
36 }
37 if (packageSemver.includes(' ') || packageSemver.includes(':')) {
38 console.warn(`warn(${installSpecifier}): Can't fetch complex semver "${packageSemver}" from remote CDN.`);
39 return null;
40 }
41 installUrlType = 'lookup';
42 installUrl = `${PIKA_CDN}/${installSpecifier}@${packageSemver}`;
43 }
44 // Hashed CDN urls never change, so its safe to grab them directly from the local cache
45 // without a network request.
46 if (installUrlType === 'pin') {
47 const cachedResult = await cacache.get.info(RESOURCE_CACHE, installUrl).catch(() => null);
48 if (cachedResult) {
49 if (cachedResult.metadata) {
50 const { pinnedUrl } = cachedResult.metadata;
51 return pinnedUrl;
52 }
53 }
54 }
55 // Otherwise, resolve from the CDN remotely.
56 const { statusCode, headers, body } = await fetchCDNResource(installUrl);
57 if (statusCode !== 200) {
58 console.warn(`Failed to resolve [${statusCode}]: ${installUrl} (${body})`);
59 console.warn(`Falling back to local copy...`);
60 return null;
61 }
62 let importUrlPath = headers['x-import-url'];
63 let pinnedUrlPath = headers['x-pinned-url'];
64 const buildStatus = headers['x-import-status'];
65 const typesUrlPath = headers['x-typescript-types'];
66 const typesUrl = typesUrlPath && `${PIKA_CDN}${typesUrlPath}`;
67 if (installUrlType === 'pin') {
68 const pinnedUrl = installUrl;
69 await cacache.put(RESOURCE_CACHE, pinnedUrl, body, {
70 metadata: { pinnedUrl, typesUrl },
71 });
72 return pinnedUrl;
73 }
74 if (pinnedUrlPath) {
75 const pinnedUrl = `${PIKA_CDN}${pinnedUrlPath}`;
76 await cacache.put(RESOURCE_CACHE, pinnedUrl, body, {
77 metadata: { pinnedUrl, typesUrl },
78 });
79 return pinnedUrl;
80 }
81 if (buildStatus === 'SUCCESS') {
82 console.warn(`Failed to lookup [${statusCode}]: ${installUrl}`);
83 console.warn(`Falling back to local copy...`);
84 return null;
85 }
86 if (!canRetry || buildStatus === 'FAIL') {
87 console.warn(`Failed to build: ${installSpecifier}@${packageSemver}`);
88 console.warn(`Falling back to local copy...`);
89 return null;
90 }
91 console.log(colors.cyan(`Building ${installSpecifier}@${packageSemver}... (This takes a moment, but will be cached for future use)`));
92 if (!importUrlPath) {
93 throw new Error('X-Import-URL header expected, but none received.');
94 }
95 const { statusCode: lookupStatusCode } = await fetchCDNResource(importUrlPath);
96 if (lookupStatusCode !== 200) {
97 throw new Error(`Unexpected response [${lookupStatusCode}]: ${PIKA_CDN}${importUrlPath}`);
98 }
99 return resolveDependency(installSpecifier, packageSemver, lockfile, false);
100}
101export async function resolveTargetsFromRemoteCDN(lockfile, config) {
102 const downloadQueue = new PQueue({ concurrency: 16 });
103 const newLockfile = { imports: {} };
104 let resolutionError;
105 for (const [installSpecifier, installSemver] of Object.entries(config.webDependencies)) {
106 downloadQueue.add(async () => {
107 try {
108 const resolvedUrl = await resolveDependency(installSpecifier, installSemver, lockfile);
109 if (resolvedUrl) {
110 newLockfile.imports[installSpecifier] = resolvedUrl;
111 }
112 }
113 catch (err) {
114 resolutionError = resolutionError || err;
115 }
116 });
117 }
118 await downloadQueue.onIdle();
119 if (resolutionError) {
120 throw resolutionError;
121 }
122 return newLockfile;
123}