"use strict" ;
const pirates = require ( "pirates" ) ;
const path = require ( "path" ) ;
const resolveFrom = require ( "resolve-from" ) ;
const syncpipe = require ( "syncpipe" ) ;
const insertCSS = require ( "insert-css" ) ;
const unreachable = require ( "@joepie91/unreachable" ) ( "icss-register" ) ;
const postcss = require ( "postcss" ) ;
const postcssLocalByDefault = require ( "postcss-modules-local-by-default" ) ;
const postcssExtractImports = require ( "postcss-modules-extract-imports" ) ;
const postcssScope = require ( "postcss-modules-scope" ) ;
const postcssValues = require ( "postcss-modules-values" ) ;
const postcssICSSParser = require ( "postcss-icss-parser" ) ;
const genericNames = require ( "generic-names" ) ;
const { validateOptions } = require ( "@validatem/core" ) ;
const isArray = require ( "@validatem/is-array" ) ;
const isBoolean = require ( "@validatem/is-boolean" ) ;
const isFunction = require ( "@validatem/is-function" ) ;
const isString = require ( "@validatem/is-string" ) ;
const oneOf = require ( "@validatem/one-of" ) ;
const arrayOf = require ( "@validatem/array-of" ) ;
const defaultTo = require ( "@validatem/default-to" ) ;
let defaultNameGenerator = genericNames ( "[name]__[local]---[hash:base64:5]" ) ;
module . exports = function registerICSS ( _options ) {
let options = validateOptions ( arguments , [ defaultTo ( { } ) , {
generateScopedName : [ isFunction , defaultTo . literal ( defaultNameGenerator ) ] ,
mode : [ oneOf ( [ "local" , "global" ] ) , defaultTo ( "local" ) ] ,
autoExportImports : [ isBoolean , defaultTo ( true ) ] ,
// TODO: Actually validate that the array items are compatible PostCSS plugins, using array-of
before : [ isArray , defaultTo ( [ ] ) ] ,
after : [ isArray , defaultTo ( [ ] ) ] ,
extensions : [ arrayOf ( isString ) , defaultTo ( [ ".css" ] ) ] ,
postcssOptions : [ defaultTo ( { } ) ] // TODO: Validate for plain object, once is-plain-object works cross-realm
} ] ) ;
let processedFiles = new Map ( ) ;
let generateScopedName = options . generateScopedName ;
function getExports ( fullPath ) {
if ( path . isAbsolute ( fullPath ) ) {
if ( processedFiles . has ( fullPath ) ) {
return processedFiles . get ( fullPath ) ;
} else {
let htmlExports = require ( fullPath ) ;
processedFiles . set ( fullPath , htmlExports ) ;
return htmlExports ;
}
} else {
throw unreachable ( ` Path must be absolute, but isn't ( ${ fullPath } ) ` ) ;
}
}
function process ( fullPath , code ) {
// We cannot reuse the PostCSS instance across files here yet, because the keyReplacer fallback is dependent on the path of the file we're currently processing.
// TODO: Figure out a sensible way to improve upon that.
let postcssInstance = postcss ( [
... options . before ,
postcssValues ,
postcssLocalByDefault ( { mode : options . mode } ) ,
postcssExtractImports ( ) ,
postcssScope ( {
generateScopedName : generateScopedName
} ) ,
postcssICSSParser ( {
autoExportImports : options . autoExportImports ,
keyReplacer : ( { url , remoteKey } ) => {
let resolvedSourcePath = resolveFrom ( path . dirname ( fullPath ) , url ) ;
let htmlExports = getExports ( resolvedSourcePath ) ;
let mangledName = htmlExports [ remoteKey ] ;
if ( mangledName == null ) {
// TODO: Error type
throw new Error ( ` No export named ' ${ remoteKey } ' found in file ${ url } ` ) ;
}
/* The replacement is to deal with the difference in multiple-class notation between CSS and HTML; in CSS they are dot-delimited, but in HTML (which the ICSS spec targets) they are space-delimited. We need the CSS notation here. */
return mangledName . replace ( / /g , "." ) ;
}
} ) ,
... options . after
] ) ;
let result = postcssInstance . process ( code , {
from : fullPath ,
... options . postcssOptions
} ) ;
let icssExports = syncpipe ( result . messages , [
( _ ) => _ . filter ( ( message ) => message . pluginName === "postcss-icss-parser" && message . type === "icss-export" ) ,
( _ ) => _ . map ( ( { item } ) => [ item . name , item . value ] ) ,
( _ ) => Object . fromEntries ( _ )
] ) ;
insertCSS ( result . css ) ;
return ` module.exports = ${ JSON . stringify ( icssExports ) } ` ;
}
let extensions = options . extensions ;
// TODO: Figure out a way to allow specifying custom PostCSS (and other transpilation?) configs from within a package
let unhook = pirates . addHook (
( code , fullPath ) => process ( fullPath , code ) ,
{ exts : extensions , ignoreNodeModules : false }
) ;
return unhook ;
} ;