UNPKG

19.8 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.INIT_TEMPLATE_FILE = exports.HMR_OVERLAY_CODE = exports.HMR_CLIENT_CODE = exports.removeTrailingSlash = exports.removeLeadingSlash = exports.addTrailingSlash = exports.addLeadingSlash = exports.removeExtension = exports.addExtension = exports.replaceExtension = exports.hasExtension = exports.getExtension = exports.isJavaScript = exports.appendHtmlToHead = exports.relativeURL = exports.jsSourceMappingURL = exports.cssSourceMappingURL = exports.sanitizePackageName = exports.isRemoteUrl = exports.getExtensionMatch = exports.findMatchingAliasEntry = exports.clearCache = exports.updateLockfileHash = exports.checkLockfileHash = exports.openInBrowser = exports.MISSING_PLUGIN_SUGGESTIONS = exports.resolveDependencyManifest = exports.parsePackageImportSpecifier = exports.isFsEventsEnabled = exports.getPackageSource = exports.isTruthy = exports.writeLockfile = exports.convertSkypackImportMapToLockfile = exports.convertLockfileToSkypackImportMap = exports.readLockfile = exports.readFile = exports.deleteFromBuildSafe = exports.SVELTE_VUE_REGEX = exports.CSS_REGEX = exports.HTML_STYLE_REGEX = exports.HTML_JS_REGEX = exports.BUILD_CACHE = exports.remotePackageSDK = exports.NATIVE_REQUIRE = exports.LOCKFILE_NAME = exports.GLOBAL_CACHE_DIR = void 0;
7const cacache_1 = __importDefault(require("cacache"));
8const cachedir_1 = __importDefault(require("cachedir"));
9const etag_1 = __importDefault(require("etag"));
10const execa_1 = __importDefault(require("execa"));
11const find_up_1 = __importDefault(require("find-up"));
12const fs_1 = __importDefault(require("fs"));
13const isbinaryfile_1 = require("isbinaryfile");
14const mkdirp_1 = __importDefault(require("mkdirp"));
15const open_1 = __importDefault(require("open"));
16const path_1 = __importDefault(require("path"));
17const rimraf_1 = __importDefault(require("rimraf"));
18const skypack_1 = require("skypack");
19const url_1 = __importDefault(require("url"));
20const local_1 = __importDefault(require("./sources/local"));
21const remote_1 = __importDefault(require("./sources/remote"));
22exports.GLOBAL_CACHE_DIR = cachedir_1.default('snowpack');
23exports.LOCKFILE_NAME = 'snowpack.deps.json';
24// We need to use eval here to prevent Rollup from detecting this use of `require()`
25exports.NATIVE_REQUIRE = eval('require');
26exports.remotePackageSDK = new skypack_1.SkypackSDK({ origin: 'https://pkg.snowpack.dev' });
27// A note on cache naming/versioning: We currently version our global caches
28// with the version of the last breaking change. This allows us to re-use the
29// same cache across versions until something in the data structure changes.
30// At that point, bump the version in the cache name to create a new unique
31// cache name.
32exports.BUILD_CACHE = path_1.default.join(exports.GLOBAL_CACHE_DIR, 'build-cache-2.7');
33const LOCKFILE_HASH_FILE = '.hash';
34// NOTE(fks): Must match empty script elements to work properly.
35exports.HTML_JS_REGEX = /(<script[^>]*?type="module".*?>)(.*?)<\/script>/gims;
36exports.HTML_STYLE_REGEX = /(<style.*?>)(.*?)<\/style>/gims;
37exports.CSS_REGEX = /@import\s*['"](.*?)['"];/gs;
38exports.SVELTE_VUE_REGEX = /(<script[^>]*>)(.*?)<\/script>/gims;
39/**
40 * Like rimraf, but will fail if "dir" is outside of your configured build output directory.
41 */
42function deleteFromBuildSafe(dir, config) {
43 const { out } = config.buildOptions;
44 if (!path_1.default.isAbsolute(dir)) {
45 throw new Error(`rimrafSafe(): dir ${dir} must be a absolute path`);
46 }
47 if (!path_1.default.isAbsolute(out)) {
48 throw new Error(`rimrafSafe(): buildOptions.out ${out} must be a absolute path`);
49 }
50 if (!dir.startsWith(out)) {
51 throw new Error(`rimrafSafe(): ${dir} outside of buildOptions.out ${out}`);
52 }
53 return rimraf_1.default.sync(dir);
54}
55exports.deleteFromBuildSafe = deleteFromBuildSafe;
56/** Read file from disk; return a string if it’s a code file */
57async function readFile(filepath) {
58 const data = await fs_1.default.promises.readFile(url_1.default.fileURLToPath(filepath));
59 const isBinary = await isbinaryfile_1.isBinaryFile(data);
60 return isBinary ? data : data.toString('utf8');
61}
62exports.readFile = readFile;
63async function readLockfile(cwd) {
64 try {
65 var lockfileContents = fs_1.default.readFileSync(path_1.default.join(cwd, exports.LOCKFILE_NAME), {
66 encoding: 'utf8',
67 });
68 }
69 catch (err) {
70 // no lockfile found, ignore and continue
71 return null;
72 }
73 // If this fails, we actually do want to alert the user by throwing
74 return JSON.parse(lockfileContents);
75}
76exports.readLockfile = readLockfile;
77function sortObject(originalObject) {
78 const newObject = {};
79 for (const key of Object.keys(originalObject).sort()) {
80 newObject[key] = originalObject[key];
81 }
82 return newObject;
83}
84function convertLockfileToSkypackImportMap(origin, lockfile) {
85 const result = { imports: {} };
86 for (const [key, val] of Object.entries(lockfile.lock)) {
87 result.imports[key.replace(/\#.*/, '')] = origin + '/' + val;
88 result.imports[key.replace(/\#.*/, '') + '/'] = origin + '/' + val + '/';
89 }
90 return result;
91}
92exports.convertLockfileToSkypackImportMap = convertLockfileToSkypackImportMap;
93function convertSkypackImportMapToLockfile(dependencies, importMap) {
94 const result = { dependencies, lock: {} };
95 for (const [key, val] of Object.entries(dependencies)) {
96 const valPath = url_1.default.parse(importMap.imports[key]).pathname;
97 result.lock[key + '#' + val] = valPath === null || valPath === void 0 ? void 0 : valPath.substr(1);
98 }
99 return result;
100}
101exports.convertSkypackImportMapToLockfile = convertSkypackImportMapToLockfile;
102async function writeLockfile(loc, importMap) {
103 importMap.dependencies = sortObject(importMap.dependencies);
104 importMap.lock = sortObject(importMap.lock);
105 fs_1.default.writeFileSync(loc, JSON.stringify(importMap, undefined, 2), { encoding: 'utf8' });
106}
107exports.writeLockfile = writeLockfile;
108function isTruthy(item) {
109 return Boolean(item);
110}
111exports.isTruthy = isTruthy;
112function getPackageSource(source) {
113 return source === 'local' ? local_1.default : remote_1.default;
114}
115exports.getPackageSource = getPackageSource;
116/**
117 * Returns true if fsevents exists. When Snowpack is bundled, automatic fsevents
118 * detection fails for many libraries. This function helps add back support.
119 */
120function isFsEventsEnabled() {
121 try {
122 exports.NATIVE_REQUIRE('fsevents');
123 return true;
124 }
125 catch (e) {
126 return false;
127 }
128}
129exports.isFsEventsEnabled = isFsEventsEnabled;
130/** Get the package name + an entrypoint within that package (if given). */
131function parsePackageImportSpecifier(imp) {
132 const impParts = imp.split('/');
133 if (imp.startsWith('@')) {
134 const [scope, name, ...rest] = impParts;
135 return [`${scope}/${name}`, rest.join('/') || null];
136 }
137 const [name, ...rest] = impParts;
138 return [name, rest.join('/') || null];
139}
140exports.parsePackageImportSpecifier = parsePackageImportSpecifier;
141/**
142 * Given a package name, look for that package's package.json manifest.
143 * Return both the manifest location (if believed to exist) and the
144 * manifest itself (if found).
145 *
146 * NOTE: You used to be able to require() a package.json file directly,
147 * but now with export map support in Node v13 that's no longer possible.
148 */
149function resolveDependencyManifest(dep, cwd) {
150 // Attempt #1: Resolve the dependency manifest normally. This works for most
151 // packages, but fails when the package defines an export map that doesn't
152 // include a package.json. If we detect that to be the reason for failure,
153 // move on to our custom implementation.
154 try {
155 const depManifest = fs_1.default.realpathSync.native(require.resolve(`${dep}/package.json`, { paths: [cwd] }));
156 return [depManifest, exports.NATIVE_REQUIRE(depManifest)];
157 }
158 catch (err) {
159 // if its an export map issue, move on to our manual resolver.
160 if (err.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
161 return [null, null];
162 }
163 }
164 // Attempt #2: Resolve the dependency manifest manually. This involves resolving
165 // the dep itself to find the entrypoint file, and then haphazardly replacing the
166 // file path within the package with a "./package.json" instead. It's not as
167 // thorough as Attempt #1, but it should work well until export maps become more
168 // established & move out of experimental mode.
169 let result = [null, null];
170 try {
171 const fullPath = fs_1.default.realpathSync.native(require.resolve(dep, { paths: [cwd] }));
172 // Strip everything after the package name to get the package root path
173 // NOTE: This find-replace is very gross, replace with something like upath.
174 const searchPath = `${path_1.default.sep}node_modules${path_1.default.sep}${dep.replace('/', path_1.default.sep)}`;
175 const indexOfSearch = fullPath.lastIndexOf(searchPath);
176 if (indexOfSearch >= 0) {
177 const manifestPath = fullPath.substring(0, indexOfSearch + searchPath.length + 1) + 'package.json';
178 result[0] = manifestPath;
179 const manifestStr = fs_1.default.readFileSync(manifestPath, { encoding: 'utf8' });
180 result[1] = JSON.parse(manifestStr);
181 }
182 }
183 catch (err) {
184 // ignore
185 }
186 finally {
187 return result;
188 }
189}
190exports.resolveDependencyManifest = resolveDependencyManifest;
191/**
192 * If Rollup erred parsing a particular file, show suggestions based on its
193 * file extension (note: lowercase is fine).
194 */
195exports.MISSING_PLUGIN_SUGGESTIONS = {
196 '.svelte': 'Try installing rollup-plugin-svelte and adding it to Snowpack (https://www.snowpack.dev/tutorials/svelte)',
197 '.vue': 'Try installing rollup-plugin-vue and adding it to Snowpack (https://www.snowpack.dev/guides/vue)',
198};
199const appNames = {
200 win32: {
201 brave: 'brave',
202 chrome: 'chrome',
203 },
204 darwin: {
205 brave: 'Brave Browser',
206 chrome: 'Google Chrome',
207 },
208 linux: {
209 brave: 'brave',
210 chrome: 'google-chrome',
211 },
212};
213async function openInExistingChromeBrowser(url) {
214 // see if Chrome process is open; fail if not
215 await execa_1.default.command('ps cax | grep "Google Chrome"', {
216 shell: true,
217 });
218 // use open Chrome tab if exists; create new Chrome tab if not
219 const openChrome = execa_1.default('osascript ../assets/openChrome.appleScript "' + encodeURI(url) + '"', {
220 cwd: __dirname,
221 stdio: 'ignore',
222 shell: true,
223 });
224 // if Chrome doesn’t respond within 3s, fall back to opening new tab in default browser
225 let isChromeStalled = setTimeout(() => {
226 openChrome.cancel();
227 }, 3000);
228 try {
229 await openChrome;
230 }
231 catch (err) {
232 if (err.isCanceled) {
233 console.warn(`Chrome not responding to Snowpack after 3s. Opening in new tab.`);
234 }
235 else {
236 console.error(err.toString() || err);
237 }
238 throw err;
239 }
240 finally {
241 clearTimeout(isChromeStalled);
242 }
243}
244async function openInBrowser(protocol, hostname, port, browser) {
245 const url = `${protocol}//${hostname}:${port}`;
246 browser = /chrome/i.test(browser)
247 ? appNames[process.platform]['chrome']
248 : /brave/i.test(browser)
249 ? appNames[process.platform]['brave']
250 : browser;
251 const isMac = process.platform === 'darwin';
252 const isBrowserChrome = /chrome|default/i.test(browser);
253 if (!isMac || !isBrowserChrome) {
254 await (browser === 'default' ? open_1.default(url) : open_1.default(url, { app: browser }));
255 return;
256 }
257 try {
258 // If we're on macOS, and we haven't requested a specific browser,
259 // we can try opening Chrome with AppleScript. This lets us reuse an
260 // existing tab when possible instead of creating a new one.
261 await openInExistingChromeBrowser(url);
262 }
263 catch (err) {
264 // if no open Chrome process, just go ahead and open default browser.
265 await open_1.default(url);
266 }
267}
268exports.openInBrowser = openInBrowser;
269async function checkLockfileHash(dir) {
270 const lockfileLoc = await find_up_1.default(['package-lock.json', 'yarn.lock']);
271 if (!lockfileLoc) {
272 return true;
273 }
274 const hashLoc = path_1.default.join(dir, LOCKFILE_HASH_FILE);
275 const newLockHash = etag_1.default(await fs_1.default.promises.readFile(lockfileLoc, 'utf8'));
276 const oldLockHash = await fs_1.default.promises.readFile(hashLoc, 'utf8').catch(() => '');
277 return newLockHash === oldLockHash;
278}
279exports.checkLockfileHash = checkLockfileHash;
280async function updateLockfileHash(dir) {
281 const lockfileLoc = await find_up_1.default(['package-lock.json', 'yarn.lock']);
282 if (!lockfileLoc) {
283 return;
284 }
285 const hashLoc = path_1.default.join(dir, LOCKFILE_HASH_FILE);
286 const newLockHash = etag_1.default(await fs_1.default.promises.readFile(lockfileLoc));
287 await mkdirp_1.default(path_1.default.dirname(hashLoc));
288 await fs_1.default.promises.writeFile(hashLoc, newLockHash);
289}
290exports.updateLockfileHash = updateLockfileHash;
291async function clearCache() {
292 return Promise.all([
293 cacache_1.default.rm.all(exports.BUILD_CACHE),
294 local_1.default.clearCache(),
295 remote_1.default.clearCache(),
296 ]);
297}
298exports.clearCache = clearCache;
299function getAliasType(val) {
300 if (isRemoteUrl(val)) {
301 return 'url';
302 }
303 return !path_1.default.isAbsolute(val) ? 'package' : 'path';
304}
305/**
306 * For the given import specifier, return an alias entry if one is matched.
307 */
308function findMatchingAliasEntry(config, spec) {
309 // Only match bare module specifiers. relative and absolute imports should not match
310 if (spec === '.' ||
311 spec === '..' ||
312 spec.startsWith('./') ||
313 spec.startsWith('../') ||
314 spec.startsWith('/') ||
315 spec.startsWith('http://') ||
316 spec.startsWith('https://')) {
317 return undefined;
318 }
319 for (const [from, to] of Object.entries(config.alias)) {
320 const isExactMatch = spec === from;
321 const isDeepMatch = spec.startsWith(addTrailingSlash(from));
322 if (isExactMatch || isDeepMatch) {
323 return {
324 from,
325 to,
326 type: getAliasType(to),
327 };
328 }
329 }
330}
331exports.findMatchingAliasEntry = findMatchingAliasEntry;
332/**
333 * Get the most specific file extension match possible.
334 */
335function getExtensionMatch(fileName, extensionMap) {
336 let extensionPartial;
337 let extensionMatch;
338 // If a full URL is given, start at the basename. Otherwise, start at zero.
339 let extensionMatchIndex = Math.max(0, fileName.lastIndexOf('/'));
340 // Grab expanded file extensions, from longest to shortest.
341 while (!extensionMatch && extensionMatchIndex > -1) {
342 extensionMatchIndex++;
343 extensionMatchIndex = fileName.indexOf('.', extensionMatchIndex);
344 extensionPartial = fileName.substr(extensionMatchIndex).toLowerCase();
345 extensionMatch = extensionMap[extensionPartial];
346 }
347 // Return the first match, if one was found. Otherwise, return undefined.
348 return extensionMatch ? [extensionPartial, extensionMatch] : undefined;
349}
350exports.getExtensionMatch = getExtensionMatch;
351function isRemoteUrl(val) {
352 var _a;
353 return val.startsWith('//') || !!((_a = url_1.default.parse(val).protocol) === null || _a === void 0 ? void 0 : _a.startsWith('http'));
354}
355exports.isRemoteUrl = isRemoteUrl;
356/**
357 * Sanitizes npm packages that end in .js (e.g `tippy.js` -> `tippyjs`).
358 * This is necessary because Snowpack can’t create both a file and directory
359 * that end in .js.
360 */
361function sanitizePackageName(filepath) {
362 const dirs = filepath.split('/');
363 const file = dirs.pop();
364 return [...dirs.map((path) => path.replace(/\.js$/i, 'js')), file].join('/');
365}
366exports.sanitizePackageName = sanitizePackageName;
367// Source Map spec v3: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.lmz475t4mvbx
368/** CSS sourceMappingURL */
369function cssSourceMappingURL(code, sourceMappingURL) {
370 return code + `/*# sourceMappingURL=${sourceMappingURL} */`;
371}
372exports.cssSourceMappingURL = cssSourceMappingURL;
373/** JS sourceMappingURL */
374function jsSourceMappingURL(code, sourceMappingURL) {
375 return code.replace(/\n*$/, '') + `\n//# sourceMappingURL=${sourceMappingURL}\n`; // strip ending lines & append source map (with linebreaks for safety)
376}
377exports.jsSourceMappingURL = jsSourceMappingURL;
378/** URL relative */
379function relativeURL(path1, path2) {
380 let url = path_1.default.relative(path1, path2).replace(/\\/g, '/');
381 if (!url.startsWith('./') && !url.startsWith('../')) {
382 url = './' + url;
383 }
384 return url;
385}
386exports.relativeURL = relativeURL;
387const CLOSING_HEAD_TAG = /<\s*\/\s*head\s*>/gi;
388/** Append HTML before closing </head> tag */
389function appendHtmlToHead(doc, htmlToAdd) {
390 const closingHeadMatch = doc.match(CLOSING_HEAD_TAG);
391 // if no <head> tag found, throw an error (we can’t load your app properly)
392 if (!closingHeadMatch) {
393 throw new Error(`No <head> tag found in HTML (this is needed to optimize your app):\n${doc}`);
394 }
395 // if multiple <head> tags found, also freak out
396 if (closingHeadMatch.length > 1) {
397 throw new Error(`Multiple <head> tags found in HTML (perhaps commented out?):\n${doc}`);
398 }
399 return doc.replace(closingHeadMatch[0], htmlToAdd + closingHeadMatch[0]);
400}
401exports.appendHtmlToHead = appendHtmlToHead;
402function isJavaScript(pathname) {
403 const ext = path_1.default.extname(pathname).toLowerCase();
404 return ext === '.js' || ext === '.mjs' || ext === '.cjs';
405}
406exports.isJavaScript = isJavaScript;
407function getExtension(str) {
408 return path_1.default.extname(str).toLowerCase();
409}
410exports.getExtension = getExtension;
411function hasExtension(str, ext) {
412 return new RegExp(`\\${ext}$`, 'i').test(str);
413}
414exports.hasExtension = hasExtension;
415function replaceExtension(fileName, oldExt, newExt) {
416 const extToReplace = new RegExp(`\\${oldExt}$`, 'i');
417 return fileName.replace(extToReplace, newExt);
418}
419exports.replaceExtension = replaceExtension;
420function addExtension(fileName, newExt) {
421 return fileName + newExt;
422}
423exports.addExtension = addExtension;
424function removeExtension(fileName, oldExt) {
425 return replaceExtension(fileName, oldExt, '');
426}
427exports.removeExtension = removeExtension;
428/** Add / to beginning of string (but don’t double-up) */
429function addLeadingSlash(path) {
430 return path.replace(/^\/?/, '/');
431}
432exports.addLeadingSlash = addLeadingSlash;
433/** Add / to the end of string (but don’t double-up) */
434function addTrailingSlash(path) {
435 return path.replace(/\/?$/, '/');
436}
437exports.addTrailingSlash = addTrailingSlash;
438/** Remove \ and / from beginning of string */
439function removeLeadingSlash(path) {
440 return path.replace(/^[/\\]+/, '');
441}
442exports.removeLeadingSlash = removeLeadingSlash;
443/** Remove \ and / from end of string */
444function removeTrailingSlash(path) {
445 return path.replace(/[/\\]+$/, '');
446}
447exports.removeTrailingSlash = removeTrailingSlash;
448exports.HMR_CLIENT_CODE = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../assets/hmr-client.js'), 'utf8');
449exports.HMR_OVERLAY_CODE = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../assets/hmr-error-overlay.js'), 'utf8');
450exports.INIT_TEMPLATE_FILE = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../assets/snowpack-init-file.js'), 'utf8');