From fd50dcf5190fe7d2bbdca59ac416af487a37c531 Mon Sep 17 00:00:00 2001 From: Chris Zuber Date: Tue, 6 Jun 2023 17:24:25 -0700 Subject: [PATCH] Add and use `@shgysk8zer0/npm-utils` --- CHANGELOG.md | 8 +++ importmap.json | 7 +- index.js | 168 +++++++++++++++------------------------------ package-lock.json | 46 +++++++------ package.json | 4 +- rollup.config.js | 7 ++ test/components.js | 1 + test/index.js | 1 + 8 files changed, 103 insertions(+), 139 deletions(-) create mode 100644 test/components.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e1014bb..324d934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.0.1] - 2023-06-06 + +### Added +- Add `@shgysk8zer0/npm-utils` + +### Changed +- Use `@shgysk8zer0/npm-utils/importmap` instead of own resolver of specifiers + ### Fixed - Update GitHub Release Action with correct permissions diff --git a/importmap.json b/importmap.json index e7406ba..5c93814 100644 --- a/importmap.json +++ b/importmap.json @@ -2,8 +2,9 @@ "imports": { "leaflet": "https://unpkg.com/leaflet@1.9.3/dist/leaflet-src.esm.js", "firebase/": "https://www.gstatic.com/firebasejs/9.16.0/", - "@shgysk8zer0/kazoo/": "https://unpkg.com/@shgysk8zer0/kazoo@0.0.5/", - "@shgysk8zer0/polyfills": "https://unpkg.com/@shgysk8zer0/polyfills@0.0.5/all.js", - "@shgysk8zer0/polyfills/": "https://unpkg.com/@shgysk8zer0/polyfills@0.0.5/" + "@shgysk8zer0/kazoo/": "https://unpkg.com/@shgysk8zer0/kazoo@0.0.16/", + "@shgysk8zer0/polyfills": "https://unpkg.com/@shgysk8zer0/polyfills@0.0.8/all.js", + "@shgysk8zer0/polyfills/": "https://unpkg.com/@shgysk8zer0/polyfills@0.0.8/", + "@shgysk8zer0/components/": "https://unpkg.com/@shgysk8zer0/components@0.0.9/" } } diff --git a/index.js b/index.js index 859c4de..ce96aa6 100644 --- a/index.js +++ b/index.js @@ -1,117 +1,62 @@ /* eslint-env node */ -import path from 'node:path'; -import fs from 'node:fs'; -import { parse } from 'yaml'; +import { fileExists, readFile } from '@shgysk8zer0/npm-utils/fs'; +import { readYAMLFile, isYAMLFile } from '@shgysk8zer0/npm-utils/yaml'; +import { readJSONFile, isJSONFile } from '@shgysk8zer0/npm-utils/json'; +import { buildImportmap, getInvalidMapError, resolveImport } from '@shgysk8zer0/npm-utils/importmap'; +import { isString, isBare } from '@shgysk8zer0/npm-utils/utils'; +import { JS as JS_MIMES } from '@shgysk8zer0/npm-utils/mimes'; +import { pathToURL } from '@shgysk8zer0/npm-utils/url'; -const URL_PREFIXES = ['http:', 'https:']; -const PATH_PREFIXES = ['/', './', '../']; -const YAML_EXTS = ['.yaml', '.yml']; -const JSON_EXTS = ['.json']; -const JS_MIMES = ['application/javascript', 'text/javascript']; - -const isYAML = path => YAML_EXTS.some(ext => path.toLowerCase().endsWith(ext)); -const isJSON = path => JSON_EXTS.some(ext => path.toLowerCase().endsWith(ext)); const isJS = type => JS_MIMES.includes(type.toLowerCase()); -const isString = str => typeof str === 'string'; -const isURL = str => isString(str) && URL_PREFIXES.some(scheme => str.startsWith(scheme)); -const isPath = str => isString(str) && PATH_PREFIXES.some(scheme => str.startsWith(scheme)); -const isBare = str => ! isPath(str) && ! isURL(str); -const externalError = specifier => new TypeError(`Import specifier "${specifier}" is present in the Rollup external config, which is not allowed. Please remove it.`); const cached = new Map(); -function buildCache({ imports }, { external } = {}) { - return Object.entries(imports).map(([key, value]) => { - if (isBare(value)) { - throw TypeError(`Resolution of specifier “${key}” was blocked by a null entry. ${value} is a bare specifier and is not allowed.`); - } else if (external instanceof Function && external(key)) { - throw externalError(key); - } else if (Array.isArray(external) && external.includes(key)) { - throw externalError(key); - } else { - return { key, value }; - } - }); -} - -function getFile(pathname = '', options = {}) { - return new Promise((resolve, reject) => { - const filepath = path.normalize(pathname); - - fs.promises.readFile(filepath, { encoding: 'utf8' }).then(file => { - try { - if (isYAML(filepath)) { - const obj = parse(file); - resolve(buildCache(obj, options)); - } else if (isJSON(filepath)) { - const obj = JSON.parse(file); - resolve(buildCache(obj, options)); - } else { - throw new Error('Unsupported file type.'); - } - } catch (error) { - reject(error); - } - }).catch(reject); - }); +async function getFile(pathname) { + if (! await fileExists(pathname)) { + throw new Error(`${pathname} not found.`); + } else if (isYAMLFile(pathname)) { + return readYAMLFile(pathname); + } else if (isJSONFile(pathname)) { + return readJSONFile(pathname); + } else { + throw new TypeError(`Unsupported file type for ${pathname}.`); + } } export function rollupImport(importMaps = []) { - const cache = new Map(); + const importmap = new Map(); const maps = Array.isArray(importMaps) ? importMaps : [importMaps]; - function getMatch(id) { - // Return exact matches first - if (cache.has(id)) { - return cache.get(id); - } else { - let isFound = false; - - // Find the longest value beginning with id - // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap#mapping_path_prefixes - const [key, value] = [...cache.entries()] - .filter(([k]) => k.endsWith('/')) - .reduce(([key1, value1], [key2, value2]) => { - const matches = id.startsWith(key2); - - if (! isFound && matches) { - isFound = true; - return [key2, value2]; - } else if (matches && key2.length > key1.length) { - return [key2, value2]; - } else { - return [key1, value1]; - } - }, []); - - if (typeof key === 'string') { - const match = id.replace(key, value); - // Update cache for future searches - cache.set(id, match); - return match; - } else { - return null; - } - } - } - return { name: '@shgysk8zer0/rollup-import', async load(path) { if (cached.has(path)) { return cached.get(path); - } else if (isPath(path)) { - const content = await fs.promises.readFile(path, { encoding: 'utf8' }); - cached.set(path, content); - return content; - } else if (isURL(path)) { - const resp = await fetch(path); - if (resp.ok && isJS(resp.headers.get('Content-Type').split(';')[0].trim())) { - const content = resp.text(); - cached.set(path, content); - return content; - } else { - return null; + } else { + switch(new URL(path).protocol) { + case 'file:': + return readFile(path.replace('file://', '')).then(content => { + cached.set(path, content); + return content; + }); + + case 'https:': + case 'http:': + return fetch(path).then(async resp => { + // console.log({ url: resp.url, status: resp.status, ok: resp.ok }); + if (! resp.ok) { + throw new Error(`<${path}> [${resp.status} ${resp.statusText}]`); + } else if (! isJS(resp.headers.get('Content-Type').split(';')[0].trim())) { + throw new TypeError(`Expected 'application/javascript' but got ${resp.headers.get('Content-Type')}`); + } else { + const content = await resp.text(); + cached.set(path, content); + return content; + } + }); + + default: + throw new TypeError(`Unsupported protocol "${path.protocol}."`); } } }, @@ -119,28 +64,27 @@ export function rollupImport(importMaps = []) { if (typeof options !== 'undefined') { const mappings = maps.map(entry => isString(entry) ? getFile(entry, options) // Load from file - : buildCache(entry, options) // Build from object + : entry // Use the Object ); - await Promise.all(mappings).then(entries => { - entries.forEach(entry => { - entry.forEach(({ key, value }) => cache.set(key, value)); - }); - }); + await buildImportmap(importmap, mappings); + const err = getInvalidMapError(importmap); + + if (err instanceof Error) { + throw err; + } } }, - resolveId(id, src, /*{ assertions, custom, isEntry }*/) { + resolveId(id, src, { /*assertions, custom,*/ isEntry }) { // @TODO: Store `options.external` and use for return value? - if (isURL(id)) { - return { id, external: false }; - } else if (isURL(src) && isPath(id)) { - return { id: new URL(id, src).href, external: false }; + if (isEntry) { + return { id: new URL(id, `file://${process.cwd()}/`).href, external: false }; } else if (isBare(id)) { - const match = getMatch(id); + const match = resolveImport(id, importmap, { base: src }); - return isString(match) ? { id: match, external: false } : null; + return match instanceof URL ? { id: match.href, external: false } : null; } else { - return null; + return { id: pathToURL(id, src).href, external: false }; } }, }; diff --git a/package-lock.json b/package-lock.json index 0f830c5..79944e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@shgysk8zer0/rollup-import", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@shgysk8zer0/rollup-import", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { - "yaml": "^2.2.2" + "@shgysk8zer0/npm-utils": "^1.0.4" }, "devDependencies": { "eslint": "^8.40.0", @@ -146,6 +146,17 @@ "node": ">= 8" } }, + "node_modules/@shgysk8zer0/npm-utils": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@shgysk8zer0/npm-utils/-/npm-utils-1.0.4.tgz", + "integrity": "sha512-qufHcgWtprAk7p8JYuEEKEjR9hFEO4TrP+uqRvmhLQkZY248aCsq2dUTpbi1sOARaw8Io9voigN2EvvYdT6w3Q==", + "dependencies": { + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -210,8 +221,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -728,7 +738,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -1138,14 +1147,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "engines": { - "node": ">= 14" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -1247,6 +1248,14 @@ "fastq": "^1.6.0" } }, + "@shgysk8zer0/npm-utils": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@shgysk8zer0/npm-utils/-/npm-utils-1.0.4.tgz", + "integrity": "sha512-qufHcgWtprAk7p8JYuEEKEjR9hFEO4TrP+uqRvmhLQkZY248aCsq2dUTpbi1sOARaw8Io9voigN2EvvYdT6w3Q==", + "requires": { + "js-yaml": "^4.1.0" + } + }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -1290,8 +1299,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "balanced-match": { "version": "1.0.2", @@ -1679,7 +1687,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -1957,11 +1964,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==" - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 22ed438..d5986ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shgysk8zer0/rollup-import", - "version": "1.0.0", + "version": "1.0.1", "description": "A RollUp plugin for importing modules from URLs, paths, and bare specifiers using import maps.", "type": "module", "engines": { @@ -46,6 +46,6 @@ "rollup": "*" }, "dependencies": { - "yaml": "^2.2.2" + "@shgysk8zer0/npm-utils": "^1.0.4" } } diff --git a/rollup.config.js b/rollup.config.js index 4698a2b..eb20909 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,13 @@ import { rollupImport } from './index.js'; export default { input: 'test/index.js', + onwarn: warning => { + if (warning.code === 'MISSING_GLOBAL_NAME' || warning.code === 'UNRESOLVED_IMPORT') { + throw new Error(warning.message); + } else if (warning.code !== 'CIRCULAR_DEPENDENCY') { + console.warn(`(!) ${warning.message}`); + } + }, plugins: [rollupImport(['importmap.json'])], output: { file: 'test/index.out.js', diff --git a/test/components.js b/test/components.js new file mode 100644 index 0000000..d300e27 --- /dev/null +++ b/test/components.js @@ -0,0 +1 @@ +import '@shgysk8zer0/components/button/share.js'; diff --git a/test/index.js b/test/index.js index 8c84ae6..9b30e25 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,5 @@ import '@shgysk8zer0/polyfills'; +import './components.js'; import { html, ready } from '@shgysk8zer0/kazoo/dom.js'; import {