diff --git a/packages/cli/src/build-single-file.ts b/packages/cli/src/build-single-file.ts index 8004d6328..48b8035db 100644 --- a/packages/cli/src/build-single-file.ts +++ b/packages/cli/src/build-single-file.ts @@ -16,6 +16,7 @@ import type { CLIDiagnostic } from './report-diagnostics'; import { errorMessages } from './messages'; import type { ModuleFormats } from './types'; import { fileToDataUri } from './file-to-data-uri'; +import type { Root } from 'postcss'; export interface BuildCommonOptions { fullOutDir: string; @@ -142,15 +143,21 @@ export function buildSingleFile({ ); } // st.css.js - const ast = includeCSSInJS - ? tryRun( - () => inlineAssetsForJsModule(res, stylable, fs), - `Inline assets failed for: ${filePath}` - ) - : res.meta.targetAst!; + const hasCssInJsFormat = moduleFormats.find( + ([format]) => format === 'cjs+css' || format === 'esm+css' + ); + + let astForCssInJs: Root; + if (includeCSSInJS || hasCssInJsFormat) { + astForCssInJs = tryRun( + () => inlineAssetsForJsModule(res, stylable, fs), + `Inline assets failed for: ${filePath}` + ); + } moduleFormats.forEach(([format, ext]) => { outputLogs.push(`${format} module`); + const { moduleType, injectCssInJs } = parseFormat(format); const moduleCssImports = collectImportsWithSideEffects(res, stylable, ext); const cssDepth = res.meta.transformCssDepth?.cssDepth ?? 1; @@ -161,22 +168,22 @@ export function buildSingleFile({ const code = generateStylableJSModuleSource( { jsExports: res.exports, - moduleType: format, + moduleType, namespace: res.meta.namespace, varType: 'var', imports: moduleCssImports, - runtimeRequest: resolveRuntimeRequest(targetFilePath, format), + runtimeRequest: resolveRuntimeRequest(targetFilePath, moduleType), }, - includeCSSInJS + includeCSSInJS || injectCssInJs ? { - css: ast.toString(), + css: astForCssInJs.toString(), depth: cssDepth, id: res.meta.namespace, runtimeId: format, } : undefined ); - const outFilePath = targetFilePath + ext; + const outFilePath = targetFilePath + (injectCssInJs ? '.inject' : '') + ext; generated.add(outFilePath); tryRun(() => fs.writeFileSync(outFilePath, code), `Write File Error: ${outFilePath}`); }); @@ -206,6 +213,7 @@ export function buildSingleFile({ relative, dirname, isAbsolute, + ensureDirectorySync: (path) => ensureDirectory(path, fs), }); } @@ -268,6 +276,7 @@ export function buildDTS({ relative, dirname, isAbsolute, + ensureDirectorySync, }: { res: StylableResults; targetFilePath: string; @@ -279,22 +288,22 @@ export function buildDTS({ relative: (from: string, to: string) => string; dirname: (p: string) => string; isAbsolute: (p: string) => boolean; + ensureDirectorySync?: (path: string) => void; }) { const dtsContent = generateDTSContent(res); const dtsPath = targetFilePath + '.d.ts'; - + const targetDir = dirname(targetFilePath); generated.add(dtsPath); outputLogs.push('output .d.ts'); - + if (ensureDirectorySync) { + tryRun(() => ensureDirectorySync(targetDir), `Write directory File Error: ${targetDir}`); + } tryRun(() => writeFileSync(dtsPath, dtsContent), `Write File Error: ${dtsPath}`); // .d.ts.map // if not explicitly defined, assumed true with "--dts" parent scope if (dtsSourceMap !== false) { - const relativeTargetFilePath = relative( - dirname(targetFilePath), - sourceFilePath || targetFilePath - ); + const relativeTargetFilePath = relative(targetDir, sourceFilePath || targetFilePath); const dtsMappingContent = generateDTSSourceMap( dtsContent, @@ -396,8 +405,9 @@ export function removeBuildProducts({ } // st.css.js moduleFormats.forEach(([format, ext]) => { + const { injectCssInJs } = parseFormat(format); outputLogs.push(`${format} module`); - const outFilePath = targetFilePath + ext; + const outFilePath = targetFilePath + (injectCssInJs ? '.inject' : '') + ext; generated.delete(outFilePath); tryRun(() => fs.unlinkSync(outFilePath), `Unlink File Error: ${outFilePath}`); }); @@ -447,3 +457,9 @@ export function getAllDiagnostics(res: StylableResults): CLIDiagnostic[] { return diagnostic; }); } + +function parseFormat(format: ModuleFormats[0][0]) { + const injectCssInJs = format.includes('+css'); + const moduleType = format.replace('+css', '') as 'esm' | 'cjs'; + return { moduleType, injectCssInJs }; +} diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 689a79ea5..86afe1430 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -23,8 +23,11 @@ export async function build( IndexGenerator = BaseIndexGenerator, cjs, cjsExt, + cjsCss, esm, esmExt, + esmCss, + copyAssets, includeCSSInJS, outputCSS, outputCSSNameTemplate, @@ -74,7 +77,7 @@ export async function build( const buildGeneratedFiles = new Set(); const sourceFiles = new Set(); const assets = new Set(); - const moduleFormats = getModuleFormats({ cjs, esm, esmExt, cjsExt }); + const moduleFormats = getModuleFormats({ cjs, esm, cjsCss, esmCss, esmExt, cjsExt }); const { runtimeCjsOutPath, runtimeEsmOutPath } = copyRuntime( inlineRuntime, @@ -365,19 +368,30 @@ export async function build( async function buildAggregatedEntities(affectedFiles: Set, generated: Set) { if (indexFileGenerator) { await indexFileGenerator.generateIndexFile(fs); - generated.add(indexFileGenerator.indexFileTargetPath); outputFiles.set(indexFileGenerator.indexFileTargetPath, affectedFiles); - } else { + } + if (copyAssets) { const generatedAssets = handleAssets(assets, projectRoot, srcDir, outDir, fs); for (const generatedAsset of generatedAssets) { generated.add(generatedAsset); } - - if (manifest) { - generateManifest(projectRoot, sourceFiles, manifest, stylable, mode, log, fs); - generated.add(manifest); - } + } + if (manifest) { + generateManifest( + (absSourcePath) => + relative( + rootDir, + join(fullOutDir, relative(fullSrcDir, absSourcePath)) + ).replace(/\\/g, '/'), + sourceFiles, + manifest, + stylable, + mode, + log, + fs + ); + generated.add(manifest); } } } @@ -494,11 +508,15 @@ export function createGenerator( function getModuleFormats({ esm, cjs, + cjsCss, + esmCss, cjsExt, esmExt, }: { esm: boolean | undefined; cjs: boolean | undefined; + cjsCss: boolean | undefined; + esmCss: boolean | undefined; cjsExt: '.cjs' | '.js' | undefined; esmExt: '.mjs' | '.js' | undefined; }): ModuleFormats { @@ -509,5 +527,11 @@ function getModuleFormats({ if (cjs) { formats.push(['cjs', cjsExt || '.js']); } + if (esmCss) { + formats.push(['esm+css', esmExt || '.mjs']); + } + if (cjsCss) { + formats.push(['cjs+css', cjsExt || '.js']); + } return formats; } diff --git a/packages/cli/src/config/resolve-options.ts b/packages/cli/src/config/resolve-options.ts index 0b91d3e99..d9a557dd0 100644 --- a/packages/cli/src/config/resolve-options.ts +++ b/packages/cli/src/config/resolve-options.ts @@ -34,11 +34,26 @@ export function getCliArguments(): Arguments { description: 'output esm module (.mjs)', defaultDescription: String(defaults.esm), }) + .option('esmCss', { + type: 'boolean', + description: 'output esm module (.inject.mjs) with inline css injection', + defaultDescription: String(defaults.esmCss), + }) .option('cjs', { type: 'boolean', description: 'output commonjs module (.js)', defaultDescription: String(defaults.cjs), }) + .option('cjsCss', { + type: 'boolean', + description: 'output commonjs module (.inject.js) with inline css injection', + defaultDescription: String(defaults.cjsCss), + }) + .option('copyAssets', { + type: 'boolean', + description: 'emit assets found in css files', + defaultDescription: String(defaults.copyAssets), + }) .option('css', { type: 'boolean', description: 'output transpiled css (.css)', @@ -235,6 +250,9 @@ export function createDefaultOptions(): BuildOptions { cjs: false, esm: false, dts: false, + esmCss: false, + cjsCss: false, + copyAssets: true, esmExt: '.mjs', cjsExt: '.js', injectCSSRequest: false, diff --git a/packages/cli/src/generate-manifest.ts b/packages/cli/src/generate-manifest.ts index fa72fa686..3f4b6c908 100644 --- a/packages/cli/src/generate-manifest.ts +++ b/packages/cli/src/generate-manifest.ts @@ -1,39 +1,53 @@ import type { Stylable } from '@stylable/core'; -import { dirname, relative } from 'path'; import { ensureDirectory, tryRun } from './build-tools'; import type { Log } from './logger'; +import type { IFileSystem } from '@file-services/types'; +import { isAbsolute } from 'path'; export function generateManifest( - rootDir: string, + remapPath: (absPath: string) => string, filesToBuild: Set, manifestOutputPath: string, stylable: Stylable, mode: string, log: Log, - fs: any + fs: IFileSystem ) { - function getBuildNamespace(stylable: Stylable, filePath: string): string { - return stylable.fileProcessor.process(filePath).namespace; + function getExistingMeta(stylable: Stylable, filePath: string) { + // skip fs check since we should not introduce new files + return ( + stylable.fileProcessor.cache[filePath]?.value || + stylable.fileProcessor.process(filePath) + ); } - const manifest = [...filesToBuild].reduce<{ + const manifest: { namespaceMapping: { [key: string]: string; }; - }>( - (manifest, filePath) => { - manifest.namespaceMapping[relative(rootDir, filePath)] = getBuildNamespace( - stylable, - filePath - ); - return manifest; - }, - { - namespaceMapping: {}, - } - ); - log(mode, 'creating manifest file: '); + cssDependencies: { + [key: string]: string[]; + }; + } = { + namespaceMapping: {}, + cssDependencies: {}, + }; + + for (const filePath of filesToBuild) { + const meta = getExistingMeta(stylable, filePath); + + const relativePath = remapPath(filePath); + manifest.namespaceMapping[relativePath] = meta.namespace; + const shallowDeps = meta.getImportStatements().map(({ from }) => { + if (isAbsolute(from)) { + return remapPath(from); + } + return from; + }); + manifest.cssDependencies[relativePath] = shallowDeps; + } + log(mode, `Creating manifest file at ${manifestOutputPath}`); tryRun( - () => ensureDirectory(dirname(manifestOutputPath), fs), + () => ensureDirectory(fs.dirname(manifestOutputPath), fs), `Ensure directory for manifest: ${manifestOutputPath}` ); tryRun( diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 97486edf9..1d34d6c89 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -139,10 +139,14 @@ export interface BuildOptions { cjs?: boolean; /** commonjs module extension */ cjsExt?: '.cjs' | '.js'; + /** cjs with inline css injection */ + cjsCss?: boolean; /** output esm module (.mjs) */ esm?: boolean; /** esm module extension */ esmExt?: '.mjs' | '.js'; + /** esm with inline css injection */ + esmCss?: boolean; /** template of the css file emitted when using outputCSS */ outputCSSNameTemplate?: string; /** should include the css in the generated JS module */ @@ -151,6 +155,8 @@ export interface BuildOptions { outputCSS?: boolean; /** should output source .st.css file to dist */ outputSources?: boolean; + /** should copy assets to dist */ + copyAssets?: boolean; /** should add namespace reference to the .st.css copy */ useNamespaceReference?: boolean; /** should inject css import in the JS module for the generated css from outputCSS */ @@ -196,4 +202,9 @@ export interface BuildContext { diagnosticsManager?: DiagnosticsManager; } -export type ModuleFormats = Array<['esm', '.js' | '.mjs'] | ['cjs', '.js' | '.cjs']>; +export type ModuleFormats = Array< + | ['esm', '.js' | '.mjs'] + | ['esm+css', '.js' | '.mjs'] + | ['cjs', '.js' | '.cjs'] + | ['cjs+css', '.js' | '.cjs'] +>; diff --git a/packages/esbuild/src/stylable-esbuild-plugin.ts b/packages/esbuild/src/stylable-esbuild-plugin.ts index b2fc64348..dd5cd3c4b 100644 --- a/packages/esbuild/src/stylable-esbuild-plugin.ts +++ b/packages/esbuild/src/stylable-esbuild-plugin.ts @@ -346,6 +346,7 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi relative, dirname, isAbsolute, + ensureDirectorySync: fs.ensureDirectorySync, }); } }