Skip to content

Commit

Permalink
feat(config): allow out file to be configurable (#36)
Browse files Browse the repository at this point in the history
allow end users to decide where the generated json file is output. to do
so, we introduce a new optional config object to the project, which is
accepted at compile time (via a stencil config file). the config file
has a single optional property, which allows users to specify the
location the generated file should end up.

the output location is slightly opinionated:
- if not set, default the name to `web-types.json`
- if there is no json extension, add `web-types.json` to the provided
  path
- paths should be reconcilable against the stencil root dir
  • Loading branch information
rwaskiewicz authored May 13, 2024
1 parent 0ad9a0f commit b555dc2
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 20 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,25 @@ export const config: Config = {

Stencil will write a `web-types.json` to your project's root directory the next time the Stencil [build task](https://stenciljs.com/docs/cli#stencil-build) is run.

## Usage
## Configuration

The `webTypesOutputTarget` output target takes an optional argument, an object literal to configure the output target.
The following are properties on that configuration object.

### `outFile`

Defaults to `StencilConfig#{rootDir}/web-types.json`.

Since v0.3.0.

Description: A string that represents the directory to place the output file.
Users may specify either a directory (e.g. '../'), a filename (e.g. 'my-types.json') or both (e.g. '../my-types.json').
If no filename ending is '.json' is provided, the output target assumes that a filename must be added to the path.
In such cases, the default 'web-types.json' will be added to the path.

It is not recommended that users use absolute paths for this setting, as this can cause errors in projects shared by more than one developer.

## Using Web Types

Once web types have been written to disk, they need to be picked up by the IDE.
Web types for your project can be picked by JetBrains IDEs by setting the `web-types` property at the root level of your project's `package.json` file:
Expand Down
85 changes: 72 additions & 13 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,85 @@
import { describe, expect, it } from 'vitest';
import { webTypesOutputTarget } from './index.js';
import { Config } from '@stencil/core/internal';
import { type WebTypesConfig, webTypesOutputTarget } from './index.js';
import { type Config } from '@stencil/core/internal';
import { join, normalize } from 'path';

describe('webTypesOutputTarget', () => {
describe('validate', () => {
it('does not throw when all required fields are set', () => {
const config: Config = {
rootDir: '/some/mocked/field',
};
describe('WebTypesConfig field validation', () => {
describe('outFile', () => {
it.each([normalize('/user/defined/directory/web-types.json'), normalize('/user/defined/directory/types.json')])(
"does not override a user-provided, absolute path, '%s'",
(expectedDir) => {
const outputTargetConfig: WebTypesConfig = { outFile: expectedDir };

expect(() => webTypesOutputTarget().validate!(config, [])).not.toThrowError();
webTypesOutputTarget(outputTargetConfig).validate!({ rootDir: '' }, []);

expect(outputTargetConfig.outFile).toBe(expectedDir);
},
);

it.each([
[normalize('./web-types.json'), normalize('~/one/two/web-types.json')],
[normalize('../web-types.json'), normalize('~/one/web-types.json')],
[normalize('../../web-types.json'), normalize('~/web-types.json')],
])("normalizes relative path '%s' against rootDir", (relPath, expected) => {
const outputTargetConfig: WebTypesConfig = { outFile: relPath };

webTypesOutputTarget(outputTargetConfig).validate!({ rootDir: normalize('~/one/two/') }, []);

expect(outputTargetConfig.outFile).toBe(expected);
});

it.each([
'~',
'/',
'.',
normalize('./.'),
normalize('~/user/defined/directory/'),
normalize('~/user/defined.directory/'),
normalize('~/user.defined.directory/'),
normalize('~/user.defined.directory/.'),
normalize('/user/defined/directory/types.txt'),
normalize('/user/defined/directory/no-trailing-slash'),
])("sets the filename if none is detected for '%s'", (dirName) => {
const expectedDir = join(`${dirName}`, 'web-types.json');
const outputTargetConfig: WebTypesConfig = { outFile: dirName };

webTypesOutputTarget(outputTargetConfig).validate!({ rootDir: '' }, []);

expect(outputTargetConfig.outFile).toBe(expectedDir);
});

it('provides a reasonable default if the user does not provide a directory', () => {
const outputTargetConfig: WebTypesConfig = {};

webTypesOutputTarget(outputTargetConfig).validate!({ rootDir: '' }, []);

expect(outputTargetConfig.outFile).toBe('web-types.json');
});
});
});

describe('no rootDir set', () => {
const EXPECTED_ERR_MSG = 'Unable to determine the Stencil root directory. Exiting without generating web types.';
describe('Stencil Config validation', () => {
it('does not throw when all required fields are set', () => {
const config: Config = {
rootDir: normalize('/some/mocked/field'),
};

it('throws an error when the root dir is set to undefined', () => {
expect(() => webTypesOutputTarget().validate!({ rootDir: undefined }, [])).toThrowError(EXPECTED_ERR_MSG);
expect(() => webTypesOutputTarget().validate!(config, [])).not.toThrowError();
});

it('throws an error when the root dir is missing', () => {
expect(() => webTypesOutputTarget().validate!({}, [])).toThrowError(EXPECTED_ERR_MSG);
describe('no rootDir set', () => {
const EXPECTED_ERR_MSG =
'Unable to determine the Stencil root directory. Exiting without generating web types.';

it('throws an error when the root dir is set to undefined', () => {
expect(() => webTypesOutputTarget().validate!({ rootDir: undefined }, [])).toThrowError(EXPECTED_ERR_MSG);
});

it('throws an error when the root dir is missing', () => {
expect(() => webTypesOutputTarget().validate!({}, [])).toThrowError(EXPECTED_ERR_MSG);
});
});
});
});
Expand Down
31 changes: 25 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import type { BuildCtx, CompilerCtx, OutputTargetCustom, Config } from '@stencil/core/internal';
import type { BuildCtx, CompilerCtx, OutputTargetCustom, Config, Diagnostic } from '@stencil/core/internal';
import { generateWebTypes } from './generate-web-types.js';
import { Diagnostic } from '@stencil/core/internal/stencil-public-compiler';
import { extname, isAbsolute, join, sep } from 'path';

/**
* A representation of the configuration object that this output target accepts at compile time
*/
export type WebTypesConfig = {
/**
* The output location of the generated JSON file.
*/
outFile?: string;
};

/**
* A Stencil output target for generating [web-types](https://github.com/JetBrains/web-types) for a Stencil project.
Expand All @@ -16,16 +26,25 @@ import { Diagnostic } from '@stencil/core/internal/stencil-public-compiler';
*
* For more information on using this output target, please see the project's README file.
*/
export const webTypesOutputTarget = (): OutputTargetCustom => ({
export const webTypesOutputTarget = (outputTargetConfig: WebTypesConfig = {}): OutputTargetCustom => ({
type: 'custom',
name: 'web-types-output-target',
validate(config: Config, _diagnostics: Diagnostic[]) {
validate(config: Config, _diagnostics: Diagnostic[]): void {
if (typeof config.rootDir === 'undefined') {
// defer to Stencil to create & load ths into its diagnostics, rather than us having to generate one ourselves
throw new Error('Unable to determine the Stencil root directory. Exiting without generating web types.');
}

if (!outputTargetConfig.outFile) {
outputTargetConfig.outFile = 'web-types.json';
} else if (extname(outputTargetConfig.outFile) !== '.json') {
outputTargetConfig.outFile = join(outputTargetConfig.outFile, 'web-types.json');
}
if (!isAbsolute(outputTargetConfig.outFile)) {
outputTargetConfig.outFile = join(config.rootDir, outputTargetConfig.outFile);
}
},
async generator(config: Config, compilerCtx: CompilerCtx, buildCtx: BuildCtx) {
async generator(config: Config, compilerCtx: CompilerCtx, buildCtx: BuildCtx): Promise<void> {
const timespan = buildCtx.createTimeSpan('generate web-types started', true);

/**
Expand All @@ -37,7 +56,7 @@ export const webTypesOutputTarget = (): OutputTargetCustom => ({
const stencilRootDirectory = config.rootDir!;
const webTypes = generateWebTypes(buildCtx, stencilRootDirectory);

await compilerCtx.fs.writeFile('web-types.json', JSON.stringify(webTypes, null, 2));
await compilerCtx.fs.writeFile(outputTargetConfig.outFile!, JSON.stringify(webTypes, null, 2));

timespan.finish('generate web-types finished');
},
Expand Down

0 comments on commit b555dc2

Please sign in to comment.