ts-const-value-transformer
v0.10.1
Published
Transforms constant variables with actual literals in TypeScript project.
Downloads
575
Maintainers
Readme
ts-const-value-transformer
Transforms constant variables with actual literals in TypeScript project. Provides 'transformer' for TypeScript building ecosystems, 'loader' for webpack, and transform and print APIs.
Motivation
Usually, constant variables will be replaced with actual values by terser, swc or etc. (swc example), but if the variables are exported, the variables are not replaced (swc example). But if the variables are strictly typed, the variables should be replaced probably safely.
This package aims to replace those variables/property references/enum values with actual values, to reduce code side (and, perhaps, to increase execution speed).
Usage
First, install with:
npm install --save-dev ts-const-value-transformerUsing transformer
Transformer is a feature for TypeScript build system to convert parsed source code (AST) to another AST.
TypeScript compiler tsc does not accept transformers, but some TypeScript-related ecosystems such as
ts-loader and ts-patch accept transformers to intercept builds.
To use with ts-loader, write webpack configuration as follows:
import { createTransformer } from 'ts-const-value-transformer';
// or: const { createTransformer } = require('ts-const-value-transformer');
// (you can use 'require' for ES modules starting from Node.js v20.19.0; see https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require)
const yourWebpackConfiguration = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
getCustomTransformers: (program) => ({
before: [
createTransformer(program, {
options: {
/* ts-const-value-transformer options (see below) */
},
}),
],
}),
// ...other configurations
},
},
],
},
],
},
// ...other configurations
};or:
const yourWebpackConfiguration = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
// If you specify only one transformer, you can use 'ts-const-value-transformer/getCustomTransformers' module
getCustomTransformers:
'ts-const-value-transformer/getCustomTransformers',
// ...other configurations
},
},
],
},
],
},
// ...other configurations
};To use with ts-patch, write tsconfig.json as follows:
"compilerOptions": {
// ...
"plugins": [
{ "transform": "ts-const-value-transformer", "options": { /* ts-const-value-transformer options (see below) */ } }
]
}Using webpack loader
You can use webpack loader for transformation. If you use loaders to transpile TS files other than ts-loader, ts-const-value-transformer/loader is a choice.
Note that this may increase the build time because of parsing processes.
If you are using ts-loader, you should use the transformer described above for performance reason.
const yourWebpackConfiguration = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-typescript'],
},
},
{
loader: 'ts-const-value-transformer/loader',
options; {
project: 'tsconfig.json', // your tsconfig.json
// typescript: 'typescript', // if you are using another 'typescript' package, you can specify the module name here
// ... other options from `TransformOptions` fields
// hoistProperty: false,
// ...
}
},
],
},
],
},
// ...other configurations
};Transform options
export interface TransformOptions {
/** `typescript` namespace object */
ts?: typeof ts;
/** Hoist property expressions (`x.prop`) which the value is constant. Default is true, but if the property getter has side effects (not recommended), set false explicitly. */
hoistProperty?: boolean | undefined;
/** Hoist TypeScript's `enum` values (which are constant). Default is true, but if you want to preserve references, set false explicitly. Note that TypeScript compiler erases `const enum` references unless `preserveConstEnums` is true. */
hoistEnumValues?: boolean | undefined;
/** Hoist values defined in the external libraries. Default is true, but if the external libraries are not bundled, set false explicitly to keep references. */
hoistExternalValues?: boolean | undefined;
/** Hoist function calls which the return value is constant. Default is false because function calls may have side effects. */
unsafeHoistFunctionCall?: boolean | undefined;
/** Hoist function calls, with `@__PURE__` (or `#__PURE__`) comment annotation (must be a multi-line comment), which the return value is constant. Default is false, but if the function really has no side effects, you can safely specify true. If true, `unsafeHoistFunctionCall` option is ignored for `@__PURE__` functions */
hoistPureFunctionCall?: boolean | undefined;
/** Hoist expressions with `as XXX`. Default is false because the base (non-`as`) value may be non-constant. */
unsafeHoistAsExpresion?: boolean | undefined;
/** Hoist properties/variables that can write (i.e. `let` / `var` variables or properies without `readonly`). Default is false because although the value is literal type at some point, the value may change to another literal type. */
unsafeHoistWritableValues?: boolean | undefined;
/** Uses `undefined` symbol for `undefined` type values. Default is false and replaces to `void 0`. */
useUndefinedSymbolForUndefinedValue?: boolean | undefined;
/** Hoist `undefined` symbol to `void 0` (or `undefined` if useUndefinedSymbolForUndefinedValue is true). Default is true. */
hoistUndefinedSymbol?: boolean | undefined;
/** Hoist template literals with constant (not including variables). Default is false because it is not necessary for ES2015 or later; Script minifiers would optimize for those values. For ES5 (which will be deprecated in the future), TypeScript converts template literals to such as `''.concat(...)`, which is not treated as constant values. */
hoistConstTemplateLiteral?: boolean | undefined;
/**
* External names (tested with `.includes()` for string, with `.test()` for RegExp) for `hoistExternalValues` settings (If `hoistExternalValues` is not specified, this setting will be used).
* - Path separators for input file name are always normalized to '/' internally.
* - Default is `['/node_modules/']`.
*/
externalNames?: ReadonlyArray<string | RegExp> | undefined;
/**
* Specifies for file name list or function to skip transformation. This option is used by webpack loader, the transformed called from ts-loader, and createPortalTransformer only.
* - For list, if the token is `string`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
* If the token is `RegExp`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
* - For function, the transformation will be skipped if `fn(fileName)` is true.
*/
ignoreFiles?:
| ReadonlyArray<string | RegExp>
| ((fileName: string) => boolean);
}Note that you must pass options field to createTransformer function or "plugins" specifier (see above examples).
APIs
import {
createTransformer,
printSource,
printSourceWithMap,
transformSource,
version,
type TransformOptions,
createPortalTransformer,
createPortalTransformerSync,
type CreatePortalTransformerOptions,
type PortalTransformer,
} from 'ts-const-value-transformer';createTransformer: (program: ts.Program, config?: { options?: TransformOptions }, extras?: { ts?: typeof ts }) => ts.TransformerFactory<ts.SourceFile>
Used with TypeScript API or ts-loader.
Call example:
import * as path from 'path';
import * as ts from 'typescript';
import { createTransformer } from 'ts-const-value-transformer';
const PROJECT_DIR = path.resolve('.');
// Load tsconfig.json file
const configFile = ts.readJsonConfigFile(
path.resolve(PROJECT_DIR, 'tsconfig.json'),
ts.sys.readFile
);
const config = ts.parseJsonSourceFileConfigFileContent(
configFile,
ts.sys,
PROJECT_DIR
);
// Initialize the program
const program = ts.createProgram({
options: config.options,
rootNames: config.fileNames,
});
// Load the source file to transform
const fileName = path.resolve(PROJECT_DIR, 'index.mts');
const sourceFile = program.getSourceFile(fileName)!;
// Do transform
const transformer = createTransformer(program);
const result = ts.transform(
sourceFile,
[transformer],
program.getCompilerOptions()
);
const transformedSource = result.transformed[0]!;
// You can emit the transformed code by using `printSource` (see below):
// console.log(printSource(transformedSource));printSource: (sourceFile: ts.SourceFile) => string
Prints (generates) source code from SourceFile. The source file must be one generated by transformSource or existing in the TS project (available with getSourceFile() in ts.Program).
printSourceWithMap: (sourceFile: ts.SourceFile, originalSourceName: string, startOfSourceMap?: RawSourceMap) => [string, RawSourceMap]
Prints (generates) source code from SourceFile, along with raw source-map data. The source file must be one generated by transformSource or existing in the TS project (available with getSourceFile() in ts.Program).
originalSourceNamewould be the file name ofsourceFile, but you can specify another name.startOfSourceMapis a base source map (if original source file is a generated-content) if available.
transformAndPrintSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions) => string
Transforms the source file with TypeScript project and prints a new source code. This acts like combination of transformSource and printSource, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
transformAndPrintSourceWithMap: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, originalSourceName: string, options?: TransformOptions, startOfSourceMap?: RawSourceMap) => [string, RawSourceMap]
Transforms the source file with TypeScript project and prints a new source code. This acts like combination of transformSource and printSourceWithMap, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
originalSourceNamewould be the file name ofsourceFile, but you can specify another name.startOfSourceMapis a base source map (if original source file is a generated-content) if available.
transformSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions) => ts.SourceFile
Transforms the source file with TypeScript project. You don't need to call this function directly; use createTransformer or createPortalTransformer instead.
Note that ignoreFiles of options will be ignored for this function.
version: string
The version string of this package.
type TransformOptions
See Transform options.
createPortalTransformer: (options?: CreatePortalTransformerOptions) => Promise
Creates 'portal transformer', which can be used the transformer easily from the code which does not use TypeScript Compiler API.
The return object has transform method with signature: (content: string, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions) => [newSource: string, newSourceMap: RawSourceMap | undefined]. You can call to transform TypeScript source code. (Note that this API does not transpile to JavaScript; the output code is still TypeScript code.)
CreatePortalTransformerOptions has a following signature. Also, TransformOptions fields, including ignoreFiles, can be used.
interface CreatePortalTransformerOptions extends TransformOptions {
/** Path to tsconfig.json. If omitted, `tsconfig.json` will be used. */
project?: string;
/** Package path to `typescript` or `typescript` namespace object. */
typescript?: string | typeof tsNamespace;
/** The current directory for file search. Also affects to `project` option. */
cwd?: string;
/**
* Speficies the count. When the transformation count reaches this value, `program` instance will be recreated (and count will be reset).
* This is useful if the project is big and out-of-memory occurs during transformation, but the process may be slower.
* If 0 or `undefined`, recreation will not be performed.
*/
recreateProgramOnTransformCount?: number;
/** Specifies to cache base (original) source code for check if the input is changed. Default is false. */
cacheBaseSource?: boolean;
/** Specifies to cache result source code. Default is true (false for webpack loader). If the latter process has cache system, specifies false to reduce memory usage. */
cacheResult?: boolean;
}If Promise cannot be used for some reason, use createPortalTransformerSync instead.
createPortalTransformerWithTsLs: (options?: CreatePortalTransformerWithTsLsOptions) => Promise
Experimental: Creates 'portal transformer' like createPortalTransformer, but uses TypeScript language server, including tsgo, as type information provider.
- To use with
tsgo, you must install it likenpm install -D @typescript/native-preview. - Currently,
typescriptpackage is also necessary for parsing source code into AST. - Also, tsconfig file must be named with
tsconfig.json(it is limitation of language server).
CreatePortalTransformerOptions has a following signature. Also, TransformOptions fields, including ignoreFiles, can be used.
export interface CreatePortalTransformerWithTsLsOptions
extends TransformOptions {
/**
* Command to run language server. The first element is used for command name and following elements are used for `argv`.
* Default is `['npx', 'tsgo', '--lsp', '--stdio']`.
*/
command?: readonly string[];
/** Path to tsconfig.json. If omitted, `tsconfig.json` will be used. **Currently `project` must be path to `tsconfig.json` file name; other than `tsconfig.json` is not supported.** */
project?: string;
/** Package path to `typescript` or `typescript` namespace object. This is still necessary to retrieve AST. */
typescript?: string | typeof tsNamespace;
/** The current directory for file search. Also affects to `project` option. */
cwd?: string;
/** Specifies to cache base (original) source code for check if the input is changed. Default is false. */
cacheBaseSource?: boolean;
/** Specifies to cache result source code. Default is true (false for webpack loader). If the latter process has cache system, specifies false to reduce memory usage. */
cacheResult?: boolean;
}Notice
Starting from v0.4.0, unsafeHoistWritableValues option is introduced. Since TypeScript sometimes narrows non-constant values to literal types such as:
const resultObject = { success: false };
someFunc1(resultObject);
console.log(resultObject.success); // resultObject.success will be `boolean` type
resultObject.success = false;
someFunc1(resultObject);
console.log(resultObject.success); // resultObject.success will be `false` type, not `boolean`... so if unsafeHoistWritableValues is true, the second reference of resultObject.success above will be replaced to false, which may not be correct.
Additional notes
I think there should be more optimization methods by using strictly-typed information, like other programming languages.
