handoff-app
Version:
Automated documentation toolchain for building client side documentation from figma
210 lines (188 loc) • 6.55 kB
text/typescript
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { InlineConfig, build as viteBuild } from 'vite';
import Handoff, { initIntegrationObject } from '../../../index';
import viteBaseConfig from '../../config';
import { getComponentOutputPath } from '../component';
import { TransformComponentTokensResult } from '../types';
const { pathToFileURL } = require('url');
/**
* Builds a CSS bundle using Vite
*
* @param options - The options object
* @param options.entry - The entry file path for the bundle
* @param options.outputPath - The directory where the bundle will be output
* @param options.outputFilename - The name of the output file
* @param options.loadPaths - Array of paths for SASS to look for imports
* @param options.handoff - The Handoff configuration object
*/
const buildCssBundle = async ({
entry,
outputPath,
outputFilename,
loadPaths,
handoff,
}: {
entry: string;
outputPath: string;
outputFilename: string;
loadPaths: string[];
handoff: Handoff;
}): Promise<void> => {
// Store the current NODE_ENV value
const oldNodeEnv = process.env.NODE_ENV;
try {
let viteConfig: InlineConfig = {
...viteBaseConfig,
build: {
...viteBaseConfig.build,
outDir: outputPath,
emptyOutDir: false,
minify: false,
rollupOptions: {
input: {
style: entry,
},
output: {
// TODO: This was an edge case where we needed to output a different filename for the CSS file
assetFileNames: outputFilename,
},
},
},
css: {
preprocessorOptions: {
scss: {
loadPaths,
quietDeps: true,
// TODO: Discuss this with Domagoj
// Maintain compatibility with older sass imports
// importers: [
// {
// findFileUrl(url) {
// console.log('findFileUrl', url);
// if (!url.startsWith('~')) return null;
// return new URL(url.substring(1), pathToFileURL('node_modules'));
// },
// },
// ],
// Use modern API settings
api: 'modern-compiler',
silenceDeprecations: ['import', 'legacy-js-api'],
},
},
},
};
// Allow configuration to be modified through hooks
if (handoff?.config?.hooks?.cssBuildConfig) {
viteConfig = handoff.config.hooks.cssBuildConfig(viteConfig);
}
await viteBuild(viteConfig);
} catch (e) {
console.log(chalk.red(`Error building CSS for ${entry}`));
throw e;
} finally {
// Restore the original NODE_ENV value
if (oldNodeEnv === 'development' || oldNodeEnv === 'production' || oldNodeEnv === 'test') {
(process.env as any).NODE_ENV = oldNodeEnv;
} else {
delete (process.env as any).NODE_ENV;
}
}
};
const buildComponentCss = async (data: TransformComponentTokensResult, handoff: Handoff, sharedStyles: string) => {
const id = data.id;
console.log('buildComponentCss ------------------------------', id);
const entry = data.entries?.scss;
if (!entry) {
return data;
}
const extension = path.extname(entry);
if (!extension) {
return data;
}
const outputPath = getComponentOutputPath(handoff);
try {
if (extension === '.scss' || extension === '.css') {
// Read the original source
const sourceContent = await fs.readFile(entry, 'utf8');
if (extension === '.scss') {
data['sass'] = sourceContent;
}
// Setup SASS load paths
const loadPaths = [
path.resolve(handoff.workingPath),
path.resolve(handoff.workingPath, 'exported', handoff.config.figma_project_id),
path.resolve(handoff.workingPath, 'node_modules'),
];
if (handoff.integrationObject?.entries?.integration) {
loadPaths.unshift(path.dirname(handoff.integrationObject.entries.integration));
}
await buildCssBundle({
entry,
outputPath,
outputFilename: `${id}.css`,
loadPaths,
handoff,
});
// Read the built CSS
const builtCssPath = path.resolve(outputPath, `${id}.css`);
if (fs.existsSync(builtCssPath)) {
const builtCss = await fs.readFile(builtCssPath, 'utf8');
data['css'] = builtCss;
// Handle shared styles
const splitCSS = builtCss.split('/* COMPONENT STYLES*/');
if (splitCSS && splitCSS.length > 1) {
data['css'] = splitCSS[1];
data['sharedStyles'] = splitCSS[0];
await fs.writeFile(path.resolve(outputPath, 'shared.css'), data['sharedStyles']);
} else {
if (!sharedStyles) {
sharedStyles = '/* These are the shared styles used in every component. */ \n\n';
}
await fs.writeFile(path.resolve(outputPath, 'shared.css'), sharedStyles);
}
}
}
} catch (e) {
console.log(chalk.red(`Error building CSS for ${id}`));
throw e;
}
return data;
};
/**
* Build the main CSS file using Vite
*/
export const buildMainCss = async (handoff: Handoff): Promise<void> => {
const outputPath = getComponentOutputPath(handoff);
const integration = initIntegrationObject(handoff)[0];
if (integration?.entries?.integration && fs.existsSync(integration.entries.integration)) {
const stat = await fs.stat(integration.entries.integration);
const entryPath = stat.isDirectory() ? path.resolve(integration.entries.integration, 'main.scss') : integration.entries.integration;
if (entryPath === integration.entries.integration || fs.existsSync(entryPath)) {
console.log(chalk.green(`Building main CSS file`));
try {
// Setup SASS load paths
const loadPaths = [
path.resolve(handoff.workingPath),
path.resolve(handoff.workingPath, 'exported', handoff.config.figma_project_id),
path.resolve(handoff.workingPath, 'node_modules'),
];
if (handoff.integrationObject?.entries?.integration) {
loadPaths.unshift(path.dirname(handoff.integrationObject.entries.integration));
}
await buildCssBundle({
entry: entryPath,
outputPath,
outputFilename: 'main.css',
loadPaths,
handoff,
});
} catch (e) {
console.log(chalk.red(`Error building main CSS`));
console.log(e);
}
}
}
};
export default buildComponentCss;