UNPKG

ng-packagr

Version:

Compile and package Angular libraries in Angular Package Format (APF)

304 lines 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LoadPathsUrlRebasingImporter = exports.ModuleUrlRebasingImporter = exports.RelativeUrlRebasingImporter = void 0; exports.sassBindWorkaround = sassBindWorkaround; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_url_1 = require("node:url"); const lexer_1 = require("./lexer"); /** * A Sass Importer base class that provides the load logic to rebase all `url()` functions * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler * reflect the final filesystem location of the output CSS file. * * This class provides the core of the rebasing functionality. To ensure that each file is processed * by this importer's load implementation, the Sass compiler requires the importer's canonicalize * function to return a non-null value with the resolved location of the requested stylesheet. * Concrete implementations of this class must provide this canonicalize functionality for rebasing * to be effective. */ class UrlRebasingImporter { /** * @param entryDirectory The directory of the entry stylesheet that was passed to the Sass compiler. * @param rebaseSourceMaps When provided, rebased files will have an intermediate sourcemap added to the Map * which can be used to generate a final sourcemap that contains original sources. */ constructor(entryDirectory) { this.entryDirectory = entryDirectory; } load(canonicalUrl) { const stylesheetPath = (0, node_url_1.fileURLToPath)(canonicalUrl); const stylesheetDirectory = (0, node_path_1.dirname)(stylesheetPath); let contents = (0, node_fs_1.readFileSync)(stylesheetPath, 'utf-8'); // Rebase any URLs that are found let updatedContents; for (const { start, end, value } of (0, lexer_1.findUrls)(contents)) { // Skip if value is empty or Webpack-specific prefix if (value.length === 0 || value[0] === '~' || value[0] === '^') { continue; } // Skip if root-relative, absolute or protocol relative url if (/^((?:\w+:)?\/\/|data:|chrome:|\/)/.test(value)) { continue; } // Skip if a fragment identifier but not a Sass interpolation if (value[0] === '#' && value[1] !== '{') { continue; } // Skip if value is value contains a function call if (/#\{.+\(.+\)\}/.test(value)) { continue; } // Sass variable usage either starts with a `$` or contains a namespace and a `.$` const valueNormalized = value[0] === '$' || /^\w+\.\$/.test(value) ? `#{${value}}` : value; const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, stylesheetDirectory); // Normalize path separators and escape characters // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax const rebasedUrl = rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&'); updatedContents !== null && updatedContents !== void 0 ? updatedContents : (updatedContents = contents); updatedContents = contents.slice(0, start) + `"${rebasedUrl}||file:${valueNormalized}"` + contents.slice(end); } if (updatedContents) { contents = updatedContents; } let syntax; switch ((0, node_path_1.extname)(stylesheetPath).toLowerCase()) { case '.css': syntax = 'css'; break; case '.sass': syntax = 'indented'; break; default: syntax = 'scss'; break; } return { contents, syntax, sourceMapUrl: canonicalUrl, }; } } /** * Provides the Sass importer logic to resolve relative stylesheet imports via both import and use rules * and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. */ class RelativeUrlRebasingImporter extends UrlRebasingImporter { constructor(entryDirectory, directoryCache = new Map()) { super(entryDirectory); this.directoryCache = directoryCache; } canonicalize(url, options) { return this.resolveImport(url, options.fromImport, true); } /** * Attempts to resolve a provided URL to a stylesheet file using the Sass compiler's resolution algorithm. * Based on https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart * @param url The file protocol URL to resolve. * @param fromImport If true, URL was from an import rule; otherwise from a use rule. * @param checkDirectory If true, try checking for a directory with the base name containing an index file. * @returns A full resolved URL of the stylesheet file or `null` if not found. */ resolveImport(url, fromImport, checkDirectory) { var _a; let stylesheetPath; try { stylesheetPath = (0, node_url_1.fileURLToPath)(url); } catch { // Only file protocol URLs are supported by this importer return null; } const directory = (0, node_path_1.dirname)(stylesheetPath); const extension = (0, node_path_1.extname)(stylesheetPath); const hasStyleExtension = extension === '.scss' || extension === '.sass' || extension === '.css'; // Remove the style extension if present to allow adding the `.import` suffix const filename = (0, node_path_1.basename)(stylesheetPath, hasStyleExtension ? extension : undefined); const importPotentials = new Set(); const defaultPotentials = new Set(); if (hasStyleExtension) { if (fromImport) { importPotentials.add(filename + '.import' + extension); importPotentials.add('_' + filename + '.import' + extension); } defaultPotentials.add(filename + extension); defaultPotentials.add('_' + filename + extension); } else { if (fromImport) { importPotentials.add(filename + '.import.scss'); importPotentials.add(filename + '.import.sass'); importPotentials.add(filename + '.import.css'); importPotentials.add('_' + filename + '.import.scss'); importPotentials.add('_' + filename + '.import.sass'); importPotentials.add('_' + filename + '.import.css'); } defaultPotentials.add(filename + '.scss'); defaultPotentials.add(filename + '.sass'); defaultPotentials.add(filename + '.css'); defaultPotentials.add('_' + filename + '.scss'); defaultPotentials.add('_' + filename + '.sass'); defaultPotentials.add('_' + filename + '.css'); } let foundDefaults; let foundImports; let hasPotentialIndex = false; let cachedEntries = this.directoryCache.get(directory); if (cachedEntries) { // If there is a preprocessed cache of the directory, perform an intersection of the potentials // and the directory files. const { files, directories } = cachedEntries; foundDefaults = [...defaultPotentials].filter(potential => files.has(potential)); foundImports = [...importPotentials].filter(potential => files.has(potential)); hasPotentialIndex = checkDirectory && !hasStyleExtension && directories.has(filename); } else { // If no preprocessed cache exists, get the entries from the file system and, while searching, // generate the cache for later requests. let entries; try { entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true }); } catch (error) { // If the containing directory does not exist return null to indicate it cannot be resolved if (error.code === 'ENOENT') { return null; } throw new Error(`Error reading directory ["${directory}"] while resolving Sass import`, { cause: error, }); } foundDefaults = []; foundImports = []; cachedEntries = { files: new Set(), directories: new Set() }; for (const entry of entries) { let isDirectory; let isFile; if (entry.isSymbolicLink()) { const stats = (0, node_fs_1.statSync)((0, node_path_1.join)(directory, entry.name)); isDirectory = stats.isDirectory(); isFile = stats.isFile(); } else { isDirectory = entry.isDirectory(); isFile = entry.isFile(); } if (isDirectory) { cachedEntries.directories.add(entry.name); // Record if the name should be checked as a directory with an index file if (checkDirectory && !hasStyleExtension && entry.name === filename) { hasPotentialIndex = true; } } if (!isFile) { continue; } cachedEntries.files.add(entry.name); if (importPotentials.has(entry.name)) { foundImports.push(entry.name); } if (defaultPotentials.has(entry.name)) { foundDefaults.push(entry.name); } } this.directoryCache.set(directory, cachedEntries); } // `foundImports` will only contain elements if `options.fromImport` is true const result = (_a = this.checkFound(foundImports)) !== null && _a !== void 0 ? _a : this.checkFound(foundDefaults); if (result !== null) { return (0, node_url_1.pathToFileURL)((0, node_path_1.join)(directory, result)); } if (hasPotentialIndex) { // Check for index files using filename as a directory return this.resolveImport(url + '/index', fromImport, false); } return null; } /** * Checks an array of potential stylesheet files to determine if there is a valid * stylesheet file. More than one discovered file may indicate an error. * @param found An array of discovered stylesheet files. * @returns A fully resolved path for a stylesheet file or `null` if not found. * @throws If there are ambiguous files discovered. */ checkFound(found) { if (found.length === 0) { // Not found return null; } // More than one found file may be an error if (found.length > 1) { // Presence of CSS files alongside a Sass file does not cause an error const foundWithoutCss = found.filter(element => (0, node_path_1.extname)(element) !== '.css'); // If the length is zero then there are two or more css files // If the length is more than one than there are two or more sass/scss files if (foundWithoutCss.length !== 1) { throw new Error('Ambiguous import detected.'); } // Return the non-CSS file (sass/scss files have priority) // https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart#L44-L47 return foundWithoutCss[0]; } return found[0]; } } exports.RelativeUrlRebasingImporter = RelativeUrlRebasingImporter; /** * Provides the Sass importer logic to resolve module (npm package) stylesheet imports via both import and * use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. */ class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter { constructor(entryDirectory, directoryCache, finder) { super(entryDirectory, directoryCache); this.finder = finder; } canonicalize(url, options) { if (url.startsWith('file://')) { return super.canonicalize(url, options); } let result = this.finder(url, options); result && (result = super.canonicalize(result.href, options)); return result; } } exports.ModuleUrlRebasingImporter = ModuleUrlRebasingImporter; /** * Provides the Sass importer logic to resolve load paths located stylesheet imports via both import and * use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. */ class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter { constructor(entryDirectory, directoryCache, loadPaths) { super(entryDirectory, directoryCache); this.loadPaths = loadPaths; } canonicalize(url, options) { if (url.startsWith('file://')) { return super.canonicalize(url, options); } let result = null; for (const loadPath of this.loadPaths) { result = super.canonicalize((0, node_url_1.pathToFileURL)((0, node_path_1.join)(loadPath, url)).href, options); if (result !== null) { break; } } return result; } } exports.LoadPathsUrlRebasingImporter = LoadPathsUrlRebasingImporter; /** * Workaround for Sass not calling instance methods with `this`. * The `canonicalize` and `load` methods will be bound to the class instance. * @param importer A Sass importer to bind. * @returns The bound Sass importer. */ function sassBindWorkaround(importer) { importer.canonicalize = importer.canonicalize.bind(importer); importer.load = importer.load.bind(importer); return importer; } //# sourceMappingURL=rebasing-importer.js.map