UNPKG

snowpack

Version:

The ESM-powered frontend build tool. Fast, lightweight, unbundled.

171 lines (170 loc) 5.85 kB
import { existsSync, readFileSync } from 'fs'; import { resolve, pathToFileURL } from 'url'; import { sourcemap_stacktrace } from './sourcemaps'; import { transform } from './transform'; import { REQUIRE_OR_IMPORT } from '../util'; function moduleInit(fn) { let promise = null; return function () { return promise || (promise = fn()); }; } // This function makes it possible to load modules from the snowpack server, for the sake of SSR. export function createLoader({ config, load }) { const cache = new Map(); const graph = new Map(); async function getModule(importer, imported, urlStack) { if (imported[0] === '/' || imported[0] === '.') { const pathname = resolve(importer, imported); if (!graph.has(pathname)) graph.set(pathname, new Set()); graph.get(pathname).add(importer); return _load(pathname, urlStack); } return moduleInit(async function () { const mod = await REQUIRE_OR_IMPORT(imported, { from: config.root || config.workspaceRoot || process.cwd(), }); return { exports: mod, css: [], }; }); } function invalidateModule(path) { // If the cache doesn't have this path, check if it's a proxy file. if (!cache.has(path) && cache.has(path + '.proxy.js')) { path = path + '.proxy.js'; } cache.delete(path); const dependents = graph.get(path); graph.delete(path); if (dependents) dependents.forEach(invalidateModule); } async function _load(url, urlStack) { if (urlStack.includes(url)) { console.warn(`Circular dependency: ${urlStack.join(' -> ')} -> ${url}`); return async () => ({ exports: null, css: [], }); } if (cache.has(url)) { return cache.get(url); } const promise = (async function () { const loaded = await load(url); return moduleInit(function () { try { return initializeModule(url, loaded, urlStack.concat(url)); } catch (e) { cache.delete(url); throw e; } }); })(); cache.set(url, promise); return promise; } async function initializeModule(url, loaded, urlStack) { const { code, deps, css, names } = transform(loaded.contents); const exports = {}; const allCss = new Set(css.map((relative) => resolve(url, relative))); const fileURL = loaded.originalFileLoc ? pathToFileURL(loaded.originalFileLoc) : null; // Load dependencies but do not execute. const depsLoaded = deps.map(async (dep) => { return { name: dep.name, init: await getModule(url, dep.source, urlStack), }; }); // Execute dependencies *in order*. const depValues = []; for await (const { name, init } of depsLoaded) { const module = await init(); module.css.forEach((dep) => allCss.add(dep)); depValues.push({ name: name, value: module.exports, }); } const args = [ { name: 'global', value: global, }, { name: 'require', value: (id) => { // TODO can/should this restriction be relaxed? throw new Error(`Use import instead of require (attempted to load '${id}' from '${url}')`); }, }, { name: names.exports, value: exports, }, { name: names.__export, value: (name, get) => { Object.defineProperty(exports, name, { get }); }, }, { name: names.__export_all, value: (mod) => { // Copy over all of the descriptors. const descriptors = Object.getOwnPropertyDescriptors(mod); Object.defineProperties(exports, descriptors); }, }, { name: names.__import, value: (source) => getModule(url, source, urlStack) .then((fn) => fn()) .then((mod) => mod.exports), }, { name: names.__import_meta, value: { url: fileURL }, }, ...depValues, ]; const fn = new Function(...args.map((d) => d.name), `${code}\n//# sourceURL=${url}`); try { fn(...args.map((d) => d.value)); } catch (e) { e.stack = await sourcemap_stacktrace(e.stack, async (address) => { if (existsSync(address)) { // it's a filepath return readFileSync(address, 'utf-8'); } try { const { contents } = await load(address); return contents; } catch (_a) { // fail gracefully } }); throw e; } return { exports, css: Array.from(allCss), }; } return { importModule: async (url) => { const init = await _load(url, []); const mod = await init(); return mod; }, invalidateModule: (url) => { invalidateModule(url); }, }; }