Skip to content

Commit

Permalink
Merge pull request #270 from apple-yagi/add-output-folder-option
Browse files Browse the repository at this point in the history
Add support for `outDir` option
  • Loading branch information
mizdra authored Nov 4, 2024
2 parents 890c199 + eb082dc commit c7eb706
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Options:
--cache Only generate .d.ts and .d.ts.map for changed files. [boolean] [default: true]
--cacheStrategy Strategy for the cache to use for detecting changed files.[choices: "content", "metadata"] [default: "content"]
--logLevel What level of logs to report. [choices: "debug", "info", "silent"] [default: "info"]
-o, --outDir Output directory for generated files. [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]

Expand Down
6 changes: 6 additions & 0 deletions packages/happy-css-modules/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export function parseArgv(argv: string[]): RunnerOptions {
default: 'info' as RunnerOptions['logLevel'],
describe: 'What level of logs to report.',
})
.option('outDir', {
type: 'string',
alias: 'o',
describe: 'Output directory for generated files.',
})
.alias('h', 'help')
.alias('v', 'version')
.version(pkgJson.version)
Expand Down Expand Up @@ -116,5 +121,6 @@ export function parseArgv(argv: string[]): RunnerOptions {
cache: parsedArgv.cache,
cacheStrategy: parsedArgv.cacheStrategy,
logLevel: parsedArgv.logLevel,
outDir: parsedArgv.outDir,
};
}
11 changes: 7 additions & 4 deletions packages/happy-css-modules/src/emitter/dts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ const locator = new Locator();
const isExternalFile = () => false;

test('getDtsFilePath', () => {
expect(getDtsFilePath('/app/src/dir/1.css', false)).toBe('/app/src/dir/1.css.d.ts');
expect(getDtsFilePath('/app/src/dir/1.scss', false)).toBe('/app/src/dir/1.scss.d.ts');
expect(getDtsFilePath('/app/src/dir/1.css', true)).toBe('/app/src/dir/1.d.css.ts');
expect(getDtsFilePath('/app/src/dir/1.scss', true)).toBe('/app/src/dir/1.d.scss.ts');
expect(getDtsFilePath('/app/src/dir/1.css', false, undefined)).toBe('/app/src/dir/1.css.d.ts');
expect(getDtsFilePath('/app/src/dir/1.scss', false, undefined)).toBe('/app/src/dir/1.scss.d.ts');
expect(getDtsFilePath('/app/src/dir/1.css', true, undefined)).toBe('/app/src/dir/1.d.css.ts');
expect(getDtsFilePath('/app/src/dir/1.scss', true, undefined)).toBe('/app/src/dir/1.d.scss.ts');
expect(getDtsFilePath('/app/src/dir/1.css', false, { cwd: '/app', outDir: 'dist' })).toBe(
'/app/dist/src/dir/1.css.d.ts',
);
});

describe('generateDtsContentWithSourceMap', () => {
Expand Down
22 changes: 17 additions & 5 deletions packages/happy-css-modules/src/emitter/dts.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { EOL } from 'os';
import { basename, parse, join } from 'path';
import path, { basename, parse, join } from 'path';
import camelcase from 'camelcase';
import { SourceNode, type CodeWithSourceMap } from 'source-map';
import { type Token } from '../locator/index.js';
import { type LocalsConvention } from '../runner.js';
import { getRelativePath, type DtsFormatOptions } from './index.js';
import { getRelativePath } from './index.js';
import type { OutDirOptions, DtsFormatOptions } from './index.js';

/**
* Get .d.ts file path.
* @param filePath The path to the source file (i.e. `/dir/foo.css`). It is absolute.
* @param arbitraryExtensions Generate `.d.css.ts` instead of `.css.d.ts`.
* @param options Output directory options
* @returns The path to the .d.ts file. It is absolute.
*/
export function getDtsFilePath(filePath: string, arbitraryExtensions: boolean): string {
export function getDtsFilePath(
filePath: string,
arbitraryExtensions: boolean,
options: OutDirOptions | undefined,
): string {
let outputFilePath = filePath;
if (options?.outDir) {
const relativePath = path.relative(options.cwd, filePath);
outputFilePath = path.resolve(options.cwd, options.outDir, relativePath);
}

if (arbitraryExtensions) {
const { dir, name, ext } = parse(filePath);
const { dir, name, ext } = parse(outputFilePath);
return join(dir, `${name}.d${ext}.ts`);
} else {
return `${filePath}.d.ts`;
return `${outputFilePath}.d.ts`;
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/happy-css-modules/src/emitter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('emitGeneratedFiles', () => {
dtsFormatOptions: undefined,
cwd: getFixturePath('/test'),
isExternalFile: () => false,
outDir: undefined,
};
beforeEach(() => {
createFixtures({
Expand Down Expand Up @@ -75,4 +76,13 @@ describe('emitGeneratedFiles', () => {
expect(mtimeForDts1).not.toEqual(mtimeForDts3); // not skipped
expect(mtimeForSourceMap1).not.toEqual(mtimeForSourceMap3); // not skipped
});
test('changes output directory by outDir', async () => {
await emitGeneratedFiles({ ...defaultArgs, outDir: 'dist' });
expect(await exists(getFixturePath('/test/dist/1.css.d.ts'))).toBeTruthy();
// A link to the source map is not embedded.
expect(await readFile(getFixturePath('/test/dist/1.css.d.ts'), 'utf8')).toEqual(
expect.not.stringContaining('//# sourceMappingURL=1.css.d.ts.map'),
);
expect(await exists(getFixturePath('/test/dist/1.css.d.ts.map'))).toBeTruthy();
});
});
21 changes: 17 additions & 4 deletions packages/happy-css-modules/src/emitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export type EmitterOptions = {
dtsFormatOptions: DtsFormatOptions | undefined;
/** Whether the file is from an external library or not. */
isExternalFile: (filePath: string) => boolean;
/** Output directory for generated files. */
outDir: string | undefined;
/** Current working directory. */
cwd: string;
};

export async function emitGeneratedFiles({
Expand All @@ -53,10 +57,12 @@ export async function emitGeneratedFiles({
emitDeclarationMap,
dtsFormatOptions,
isExternalFile,
outDir,
cwd,
}: EmitterOptions): Promise<void> {
const arbitraryExtensions = dtsFormatOptions?.arbitraryExtensions ?? DEFAULT_ARBITRARY_EXTENSIONS;
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions);
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions);
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions, { outDir, cwd });
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions, { outDir, cwd });
const { dtsContent, sourceMap } = generateDtsContentWithSourceMap(
filePath,
dtsFilePath,
Expand Down Expand Up @@ -84,9 +90,11 @@ export async function isGeneratedFilesExist(
filePath: string,
emitDeclarationMap: boolean | undefined,
arbitraryExtensions: boolean,
outDir: string | undefined,
cwd: string,
): Promise<boolean> {
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions);
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions);
const dtsFilePath = getDtsFilePath(filePath, arbitraryExtensions, { outDir, cwd });
const sourceMapFilePath = getSourceMapFilePath(filePath, arbitraryExtensions, { outDir, cwd });
if (emitDeclarationMap && !(await exists(sourceMapFilePath))) {
return false;
}
Expand All @@ -95,3 +103,8 @@ export async function isGeneratedFilesExist(
}
return true;
}

export type OutDirOptions = {
outDir: string | undefined;
cwd: string;
};
8 changes: 4 additions & 4 deletions packages/happy-css-modules/src/emitter/source-map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { EOL } from 'os';
import { getSourceMapFilePath, generateSourceMappingURLComment } from './source-map.js';

test('getSourceMapFilePath', () => {
expect(getSourceMapFilePath('/app/src/dir/1.css', false)).toBe('/app/src/dir/1.css.d.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.scss', false)).toBe('/app/src/dir/1.scss.d.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.css', true)).toBe('/app/src/dir/1.d.css.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.scss', true)).toBe('/app/src/dir/1.d.scss.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.css', false, undefined)).toBe('/app/src/dir/1.css.d.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.scss', false, undefined)).toBe('/app/src/dir/1.scss.d.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.css', true, undefined)).toBe('/app/src/dir/1.d.css.ts.map');
expect(getSourceMapFilePath('/app/src/dir/1.scss', true, undefined)).toBe('/app/src/dir/1.d.scss.ts.map');
});

test('generateSourceMappingURLComment', () => {
Expand Down
10 changes: 8 additions & 2 deletions packages/happy-css-modules/src/emitter/source-map.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { EOL } from 'os';
import { getDtsFilePath } from './dts.js';
import type { OutDirOptions } from './index.js';
import { getRelativePath } from './index.js';

/**
* Get .d.ts.map file path.
* @param filePath The path to the source file (i.e. `foo.css`). It is absolute.
* @param arbitraryExtensions Generate `.d.css.ts` instead of `.css.d.ts`.
* @param outDir Output directory for generated files.
* @returns The path to the .d.ts.map file. It is absolute.
*/
export function getSourceMapFilePath(filePath: string, arbitraryExtensions: boolean): string {
return `${getDtsFilePath(filePath, arbitraryExtensions)}.map`;
export function getSourceMapFilePath(
filePath: string,
arbitraryExtensions: boolean,
options: OutDirOptions | undefined,
): string {
return `${getDtsFilePath(filePath, arbitraryExtensions, options)}.map`;
}

export function generateSourceMappingURLComment(dtsFilePath: string, sourceMapFilePath: string): string {
Expand Down
23 changes: 23 additions & 0 deletions packages/happy-css-modules/src/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,26 @@ test('support symlink', async () => {
`"{"version":3,"sources":["./1.css"],"names":["a"],"mappings":"AAAA;AAAA,E,aAAAA,G,WAAA;AAAA;AAAA","file":"1.css.d.ts","sourceRoot":""}"`,
);
});

test('changes output directory by outDir', async () => {
createFixtures({
'/test/1.css': '.a {}',
});

await run({ ...defaultOptions, outDir: getFixturePath('/dist'), cache: false, watch: false });

expect(await exists(getFixturePath('/dist/test/1.css.d.ts'))).toBe(true);
expect(await exists(getFixturePath('/dist/test/1.css.d.ts.map'))).toBe(true);

expect(await readFile(getFixturePath('/dist/test/1.css.d.ts'), 'utf8')).toMatchInlineSnapshot(`
"declare const styles:
& Readonly<{ "a": string }>
;
export default styles;
//# sourceMappingURL=./1.css.d.ts.map
"
`);
expect(await readFile(getFixturePath('/dist/test/1.css.d.ts.map'), 'utf8')).toMatchInlineSnapshot(
`"{"version":3,"sources":["../../test/1.css"],"names":["a"],"mappings":"AAAA;AAAA,E,aAAAA,G,WAAA;AAAA;AAAA","file":"1.css.d.ts","sourceRoot":""}"`,
);
});
6 changes: 6 additions & 0 deletions packages/happy-css-modules/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export interface RunnerOptions {
logLevel?: 'debug' | 'info' | 'silent' | undefined;
/** Working directory path. */
cwd?: string | undefined;
/** Output directory for generated files. */
outDir?: string | undefined;
}

type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K]: V };
Expand Down Expand Up @@ -140,6 +142,8 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
filePath,
options.declarationMap,
options.arbitraryExtensions ?? DEFAULT_ARBITRARY_EXTENSIONS,
options.outDir,
cwd,
);
const _isChangedFile = await isChangedFile(filePath);
// Generate .d.ts and .d.ts.map only when the file has been updated.
Expand All @@ -159,6 +163,8 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
arbitraryExtensions: options.arbitraryExtensions,
},
isExternalFile,
outDir: options.outDir,
cwd,
});
logger.info(chalk.green(`${relative(cwd, filePath)} (generated)`));

Expand Down

0 comments on commit c7eb706

Please sign in to comment.