Skip to content
This repository was archived by the owner on Oct 30, 2020. It is now read-only.

Support for placing .d.ts files in a different directory #44

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,52 @@ e.g.:
```

### `banner`-option
To add a "banner" prefix to each generated `*.d.ts` file, you can pass a string to this option as shown below. The prefix is quite literally prefixed into the generated file, so please ensure it conforms to the type definition syntax.
To add a "banner" prefix to each generated `.d.ts` file, you can pass a string to this option as shown below. The prefix is quite literally prefixed into the generated file, so please ensure it conforms to the type definition syntax.

```js
{ test: /\.css$/, loader: 'typings-for-css-modules?banner="// This file is automatically generated by typings-for-css-modules.\n// Please do not change this file!"' }
```

### `pathPrefix`-option
By default, `.d.ts` files are generated in the same directory as the CSS module source. To place them in a different directory, pass the `pathPrefix` option as a string or function:
* String:
* The path prefix must i) not be absolute, and ii) the imported CSS module must lie within the [webpack context](https://webpack.github.io/docs/configuration.html#context). Otherwise the `.d.ts` will be generated in the same directory as the CSS module, as though the `pathPrefix` option was ignored. The reason here is that there isn't a straightforward compiler configuration that will allow TypeScript to find the type definition files in the above two cases.
* Paths satisfying the above constraint result in generated `.d.ts` files placed in a directory of the given name relative to the webpack context, wedged before the module path (see example, next, and compiler configuration below).
* For example, if your webpack context is `./src`, the `pathPrefix` option is `generated/styles`, and your CSS module is imported as `component/widget.css`, then the generated file will be `./src/generated/styles/component/widget.d.ts`.
* Function:
* You have full control of the generated `.d.ts` path and filename.
* The function interface is given by `(sourcePath: string, contextPath: string) => string` where `sourcePath` is the path to the source CSS module, and `contextPath` is the webpack context path. The function must return the full path (including filename) of the generated `.d.ts` file, or `undefined` to abort with error. The loader will create any intermediate folders as is required to write the generated file.

When placing the `.d.ts` files in a directory separate to the CSS module, you also need to configure the TypeScript compiler to find these type definitions. You can do this by adding a `paths` section to your tsconfig.json `compilerOptions`.

For example, say we have a project having all `.ts` files in the `src` tree, and where generated `.d.ts` files should be placed into the `src/generated` tree. Our basic `tssconfig.json` is then as follows:
```json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"*": [
"*",
"generated/*"
]
}
}
}
```
where it the [webpack context](https://webpack.github.io/docs/configuration.html#context) is also configured to be `path.resolve(__dirname, "src")`.

Finally, when importing your CSS modules, use a path relative to your `baseUrl` path for import. For example, use
```ts
// [./src/component/widget.ts]
import * as styles from "component/widget.css"; // do this
```
instead of
```ts
// [./src/component/widget.ts]
import * as styles from "./widget.css"; // don't do this
```
otherwise TypeScript may not search the configured paths for the associated `.d.ts` file.

## Usage

Keep your `webpack.config` as is just instead of using `css-loader` use `typings-for-css-modules-loader`
Expand Down
50 changes: 46 additions & 4 deletions src/cssModuleToInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,52 @@ export const filterNonWordClasses = (cssModuleKeys) => {
return [filteredClassNames, nonWordClassNames,];
};

export const filenameToTypingsFilename = (filename) => {
const dirName = path.dirname(filename);
const baseName = path.basename(filename);
return path.join(dirName, `${baseName}.d.ts`);
export const filenameToTypingsFilename = (filename, pathPrefix, contextPath) => {
// Default implementation: co-locate .d.ts with the resource
const pathPrefixCallbackDefault = function (filename) {
const dirName = path.dirname(filename);
const baseName = path.basename(filename);
return path.join(dirName, baseName + '.d.ts');
};

let pathPrefixCallback = pathPrefixCallbackDefault;

if (pathPrefix) {
switch (typeof pathPrefix) {
case 'string': {
if (pathPrefix.endsWith(path.sep)) {
pathPrefix = pathPrefix.slice(0, -1);
}
let dirName = path.dirname(filename);
const baseName = path.basename(filename);
// There are three cases here:
// 1. filename is within the webpack context: add the prefix
if (dirName.startsWith(contextPath)) {
dirName = dirName.slice(contextPath.length);
pathPrefixCallback = function () {
return path.join(contextPath, pathPrefix, dirName, baseName + '.d.ts');
};
}
// 2. pathPrefix is absolute
// 3. filename is outside the webpack context
else {
// Use the default implementation for co-locating the .d.ts
}
break;
}

case 'function':
// Use the user-configured filename mapping function
pathPrefixCallback = pathPrefix;
break;

default:
// Bad configuration
return undefined;
}
}

return pathPrefixCallback(filename, contextPath);
};

export const generateNamedExports = (cssModuleKeys) => {
Expand Down
7 changes: 5 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ module.exports = function(...input) {
return callback(err);
}
const filename = this.resourcePath;
const cssModuleInterfaceFilename = filenameToTypingsFilename(filename);
const cssModuleInterfaceFilename = filenameToTypingsFilename(filename, query.pathPrefix, this.options.context);
if (!cssModuleInterfaceFilename) {
return callback('invalid typings filename; check your typings-for-css-modules loader configuration.');
}

const keyRegex = /"([^\\"]+)":/g;
let match;
const cssModuleKeys = [];

while (match = keyRegex.exec(content)) {
while ((match = keyRegex.exec(content))) {
if (cssModuleKeys.indexOf(match[1]) < 0) {
cssModuleKeys.push(match[1]);
}
Expand Down
15 changes: 15 additions & 0 deletions src/persist.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import fs from 'graceful-fs';
import os from 'os';
import path from 'path';

export const writeToFileIfChanged = (filename, content) => {
// Create the containing folder, as required
const dirName = path.dirname(filename);
if (!fs.existsSync(dirName)) {
const sep = path.sep;
const initDir = path.isAbsolute(dirName) ? sep : '';
dirName.split(sep).reduce(function (parentDir, childDir) {
const curDir = path.resolve(parentDir, childDir);
if (!fs.existsSync(curDir)) {
fs.mkdirSync(curDir);
}
return curDir;
}, initDir);
}

if (fs.existsSync(filename)) {
const currentInput = fs.readFileSync(filename, 'utf-8');

Expand Down