import type * as d from '../../declarations' ; import { basename , dirname , isAbsolute , join , relative } from 'path' ; import { buildError , normalizePath } from '@utils' ; import { getModuleId } from '../sys/resolve/resolve-utils' ; import { parseStyleDocs } from '../docs/style-docs' ; import { resolveModuleIdAsync } from '../sys/resolve/resolve-module-async' ; import { stripCssComments } from './style-utils' ; export const parseCssImports = async ( config , compilerCtx , buildCtx , srcFilePath , resolvedFilePath , styleText , styleDocs ? ) => { const isCssEntry = resolvedFilePath . toLowerCase ( ) . endsWith ( '.css' ) ; const allCssImports = [ ] ; const concatStyleText = await updateCssImports ( config , compilerCtx , buildCtx , isCssEntry , srcFilePath , resolvedFilePath , styleText , allCssImports , new Set ( ) , styleDocs ) ; return { imports : allCssImports , styleText : concatStyleText , } ; } ; const updateCssImports = async ( config , compilerCtx , buildCtx , isCssEntry , srcFilePath , resolvedFilePath , styleText , allCssImports , noLoop , styleDocs ? ) => { if ( noLoop . has ( resolvedFilePath ) ) { return styleText ; } noLoop . add ( resolvedFilePath ) ; if ( styleDocs != null ) { parseStyleDocs ( styleDocs , styleText ) ; } const cssImports = await getCssImports ( config , compilerCtx , buildCtx , resolvedFilePath , styleText ) ; if ( cssImports . length === 0 ) { return styleText ; } for ( const cssImport of cssImports ) { if ( ! allCssImports . includes ( cssImport . filePath ) ) { allCssImports . push ( cssImport . filePath ) ; } } await Promise . all ( cssImports . map ( async ( cssImportData ) => { await concatCssImport ( config , compilerCtx , buildCtx , isCssEntry , srcFilePath , cssImportData , allCssImports , noLoop , styleDocs ) ; } ) ) ; return replaceImportDeclarations ( styleText , cssImports , isCssEntry ) ; } ; const concatCssImport = async ( config , compilerCtx , buildCtx , isCssEntry , srcFilePath , cssImportData , allCssImports , noLoop , styleDocs ? ) => { cssImportData . styleText = await loadStyleText ( compilerCtx , cssImportData ) ; if ( typeof cssImportData . styleText === 'string' ) { cssImportData . styleText = await updateCssImports ( config , compilerCtx , buildCtx , isCssEntry , cssImportData . filePath , cssImportData . filePath , cssImportData . styleText , allCssImports , noLoop , styleDocs ) ; } else { const err = buildError ( buildCtx . diagnostics ) ; err . messageText = `Unable to read css import: ${ cssImportData . srcImport }` ; err . absFilePath = srcFilePath ; } } ; const loadStyleText = async ( compilerCtx , cssImportData ) => { let styleText = null ; try { styleText = await compilerCtx . fs . readFile ( cssImportData . filePath ) ; } catch ( e ) { if ( cssImportData . altFilePath ) { try { styleText = await compilerCtx . fs . readFile ( cssImportData . filePath ) ; } catch ( e ) { } } } return styleText ; } ; export const getCssImports = async ( config , compilerCtx , buildCtx , filePath , styleText ) => { const imports = [ ] ; if ( ! styleText . includes ( '@import' ) ) { return imports ; } styleText = stripCssComments ( styleText ) ; const dir = dirname ( filePath ) ; const importeeExt = filePath . split ( '.' ) . pop ( ) . toLowerCase ( ) ; let r ; const IMPORT_RE = /(@import)\s+(url\()?\s?(.*?)\s?\)?([^;]*);?/gi ; while ( ( r = IMPORT_RE . exec ( styleText ) ) ) { const cssImportData = { srcImport : r [ 0 ] , url : r [ 4 ] . replace ( /[\"\'\)]/g , '' ) , } ; if ( ! isLocalCssImport ( cssImportData . srcImport ) ) { config . logger . debug ( `did not resolve external css @import: ${ cssImportData . srcImport }` ) ; continue ; } if ( isCssNodeModule ( cssImportData . url ) ) { await resolveCssNodeModule ( config , compilerCtx , buildCtx . diagnostics , filePath , cssImportData ) ; } else if ( isAbsolute ( cssImportData . url ) ) { cssImportData . filePath = normalizePath ( cssImportData . url ) ; } else { cssImportData . filePath = normalizePath ( join ( dir , cssImportData . url ) ) ; } if ( importeeExt !== 'css' && ! cssImportData . filePath . toLowerCase ( ) . endsWith ( '.css' ) ) { cssImportData . filePath += `.${ importeeExt }` ; if ( importeeExt === 'scss' ) { const fileName = '_' + basename ( cssImportData . filePath ) ; const dirPath = dirname ( cssImportData . filePath ) ; cssImportData . altFilePath = normalizePath ( join ( dirPath , fileName ) ) ; } } if ( typeof cssImportData . filePath === 'string' ) { imports . push ( cssImportData ) ; } } return imports ; } ; export const isCssNodeModule = ( url ) => url . startsWith ( '~' ) ; export const resolveCssNodeModule = async ( config , compilerCtx , diagnostics , filePath , cssImportData ) => { try { const m = getModuleId ( cssImportData . url ) ; const resolved = await resolveModuleIdAsync ( config . sys , compilerCtx . fs , { moduleId : m . moduleId , containingFile : filePath , exts : [ ] , packageFilter : ( pkg ) => { if ( m . filePath !== '' ) { pkg . main = m . filePath ; } return pkg ; } , } ) ; cssImportData . filePath = resolved . resolveId ; cssImportData . updatedImport = `@import "${ cssImportData . filePath }";` ; } catch ( e ) { const d = buildError ( diagnostics ) ; d . messageText = `Unable to resolve node module for CSS @import: ${ cssImportData . url }` ; d . absFilePath = filePath ; } } ; export const isLocalCssImport = ( srcImport ) => { srcImport = srcImport . toLowerCase ( ) ; if ( srcImport . includes ( 'url(' ) ) { srcImport = srcImport . replace ( /\"/g , '' ) ; srcImport = srcImport . replace ( /\'/g , '' ) ; srcImport = srcImport . replace ( /\s/g , '' ) ; if ( srcImport . includes ( 'url(http' ) || srcImport . includes ( 'url(//' ) ) { return false ; } } return true ; } ; export const replaceNodeModuleUrl = ( baseCssFilePath , moduleId , nodeModulePath , url ) => { nodeModulePath = normalizePath ( dirname ( nodeModulePath ) ) ; url = normalizePath ( url ) ; const absPathToNodeModuleCss = normalizePath ( url . replace ( `~${ moduleId }` , nodeModulePath ) ) ; const baseCssDir = normalizePath ( dirname ( baseCssFilePath ) ) ; const relToRoot = normalizePath ( relative ( baseCssDir , absPathToNodeModuleCss ) ) ; return relToRoot ; } ; export const replaceImportDeclarations = ( styleText , cssImports , isCssEntry ) => { for ( const cssImport of cssImports ) { if ( isCssEntry ) { if ( typeof cssImport . styleText === 'string' ) { styleText = styleText . replace ( cssImport . srcImport , cssImport . styleText ) ; } } else if ( typeof cssImport . updatedImport === 'string' ) { styleText = styleText . replace ( cssImport . srcImport , cssImport . updatedImport ) ; } } return styleText ; } ;