Skip to content

Commit

Permalink
feat: support config-file option and multi argument input files
Browse files Browse the repository at this point in the history
  • Loading branch information
jooy2 committed Dec 5, 2024
1 parent f7577f1 commit b6c2ca5
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 47 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.1.0 (2024-12-05)

- Support `config-file` option
- Support for multi-argument input files
- Add missing options

## 1.0.0 (2024-12-03)

- Initial release
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,56 @@

A CLI module that allows you to work with multiple files at once using the glob pattern in the terser cli.

[Terser](https://terser.org) It's the same as how to use CLI, but there are a few differences in this module.

- You can use the `glob` pattern for file paths (paths must be enclosed in quotes)

To learn how to use the Terser CLI, follow these links: https://github.com/terser/terser?tab=readme-ov-file#command-line-usage

## How-to-use

[Terser](https://terser.org) It's the same as how to use CLI, but there are a few differences in this module.

- You can use the `glob` pattern for file paths (paths must be enclosed in quotes). You can also specify each file individually without using the glob pattern.
- If an output path is set, it must be a directory. We currently do not support combining multiple files into one.

```text
terser-glob filePathOrGlobPattern [terser-cli-options]
```

```shell
# Example 1
$ terser-glob dist/index.js --mangle
$ terser-glob dist/index.js dist/hello.js dist/world.js --mangle
# Example 2
$ terser-glob "dist/*.js"
$ terser-glob "dist/*.js" dist/hello.js
# Example 3
$ terser-glob "dist/**/*.js" --ie8 --mangle
$ terser-glob "dist/**/*.js" --ie8 --mangle --output result/
# Example 4
$ terser-glob "dist/abc/*.js" "dist/def/*" --config-file terser.config.json
```

## Limitations & TODO
```shell
Usage: terser-glob [options] [files...]

- Using multiple `glob` pattern arguments. Currently, only one argument is recognized.
- The `config-file` option does not currently work; each option must use a parameter.
- Some CLI options are not supported.
- Need a complex test.
Options:
-V, --version output the version number
-p, --parse <options> Specify parser options.
-c, --compress [options] Enable compressor/specify compressor options.
-m, --mangle [options] Mangle names/specify mangler options.
--mangle-props [options] Mangle properties/specify mangler options.
-f, --format [options] Format options.
-b, --beautify [options] Alias for --format.
-o, --output <file> Output file (default STDOUT).
--comments [filter] Preserve copyright comments in the output.
--config-file <file> Read minify() options from JSON file.
--ecma <version> Specify ECMAScript release: 5, 2015, 2016 or 2017...
-e, --enclose [arg[,...][:value[,...]]] Embed output in a big function with configurable arguments and values.
--ie8 Support non-standard Internet Explorer 8.
--keep-classnames Do not mangle/drop class names.
--keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.
--module Input is an ES6 module
--name-cache <file> File to hold mangled name mappings.
--safari10 Support non-standard Safari 10.
--source-map [options] Enable source map/specify source map options.
--toplevel Compress and/or mangle variables in toplevel scope.
-h, --help output usage information
```
## Contribute
Expand Down
150 changes: 115 additions & 35 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,150 @@
#!/usr/bin/env node

import meow from 'meow';
import type { Options } from 'meow';
import { minify } from 'terser';
import type { MinifyOutput } from 'terser';
import meow, { type Result, Options } from 'meow';
import { minify, type MinifyOptions, MinifyOutput } from 'terser';
import { glob } from 'glob';
import { join } from 'path';
import { readFile, writeFile } from 'fs/promises';
import { join, dirname } from 'path';
import { access, readFile, writeFile, mkdir } from 'fs/promises';
import { constants } from 'fs';

const helpText: string = [
'Usage',
' terser-glob path [terser-cli-options]',
'',
" When using a glob rule in a path, you must use quotes. ('abc/*.js'...) "
" When using a glob rule in a path, you must use quotes. ('abc/*.js'...) ",
' -V, --version output the version number',
' -p, --parse <options> Specify parser options.',
' -c, --compress [options] Enable compressor/specify compressor options.',
' -m, --mangle [options] Mangle names/specify mangler options.',
' --mangle-props [options] Mangle properties/specify mangler options.',
' -f, --format [options] Format options.',
' -b, --beautify [options] Alias for --format.',
' -o, --output <file> Output file (default STDOUT).',
' --comments [filter] Preserve copyright comments in the output.',
' --config-file <file> Read minify() options from JSON file.',
' --ecma <version> Specify ECMAScript release: 5, 2015, 2016 or 2017...',
' -e, --enclose [arg[,...][:value[,...]]] Embed output in a big function with configurable arguments and values.',
' --ie8 Support non-standard Internet Explorer 8.',
' --keep-classnames Do not mangle/drop class names.',
' --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.',
' --module Input is an ES6 module',
' --name-cache <file> File to hold mangled name mappings.',
' --safari10 Support non-standard Safari 10.',
' --source-map [options] Enable source map/specify source map options.',
' --toplevel Compress and/or mangle variables in toplevel scope.',
// From `terser-glob`
' --debug Print job progress to the console',
' -h, --help output usage information'
].join('\n');

const meowOptions: Options<any> = {
importMeta: import.meta,
help: helpText
};

const cli = meow(meowOptions);
const cli: Result<any> = meow(meowOptions);

function print(message: string): void {
process.stdout.write(message);
}

async function isFileExists(filePath: string): Promise<boolean> {
try {
await access(filePath, constants.F_OK);
return true;
} catch (error) {
return false;
}
}

async function run(): Promise<void> {
if (!cli.input[0]) {
if (!cli) {
throw new Error('Unknown error');
}

if (cli.flags.version || cli.flags.V) {
print(
`terser-glob does not provide version information; check the modules you have installed.`
);
return;
}

if (cli.input?.length < 1) {
throw new Error('No input file specified');
}

const currentPath = cli.input[0].replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1');
const files = await glob(currentPath, {
const inputFilePaths = [];

for (let i = 0; i < cli.input.length; i += 1) {
inputFilePaths[i] = cli.input[i].replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1');
}

const files = await glob(inputFilePaths, {
cwd: process.cwd()
});

const configFilePath: string | undefined = (cli.flags.configFile as string) || undefined;

const defaultConfigs: MinifyOptions = JSON.parse(
JSON.stringify({
ecma: cli.flags.ecma || undefined,
enclose: cli.flags.enclose || cli.flags.e || false,
parse: cli.flags.parse || cli.flags.p || {},
compress: cli.flags.compress || cli.flags.c || {},
mangle: cli.flags.mangle || cli.flags.m || true,
module: cli.flags.module || false,
format: cli.flags.beautify || cli.flags.b || cli.flags.format || cli.flags.f || null,
sourceMap: cli.flags.sourceMap || false,
toplevel: cli.flags.toplevel || false,
nameCache: cli.flags.nameCache || null,
ie8: cli.flags.ie8 || false,
keep_classnames: cli.flags.keepClassnames || undefined,
keep_fnames: cli.flags.keepFnames || false,
safari10: cli.flags.safari10 || false
})
);

if (configFilePath) {
try {
const configFileContent = await readFile(configFilePath, 'utf-8');
const jsonData = JSON.parse(configFileContent);

for (let i = 0; i < Object.entries(jsonData).length; i += 1) {
const [key, value] = Object.entries(jsonData)[i];

// @ts-ignore
defaultConfigs[key] = value;
}
} catch (err) {
throw new Error(`Error reading or parsing the configuration file: ${err}`);
}
}

if (cli.flags.debug) {
print(JSON.stringify(defaultConfigs, null, 2));
}

for (let i = 0; i < files.length; i += 1) {
const fileName = files[i];
const outputPath: any = cli.flags.output || cli.flags.o;
let filePath = fileName;

const newFilePath = outputPath ? join(outputPath, fileName) : fileName;
if (cli.flags.output || cli.flags.o) {
filePath = join((cli.flags.output || cli.flags.o) as string, fileName);
}

const originalCode: any = {};
originalCode[fileName] = await readFile(fileName, 'utf-8');

const minifyResult: MinifyOutput = await minify(
originalCode,
JSON.parse(
JSON.stringify({
ecma: cli.flags.ecma || undefined,
enclose: cli.flags.enclose || cli.flags.e || false,
parse: cli.flags.parse || cli.flags.p || {},
compress: cli.flags.compress || cli.flags.c || {},
mangle: cli.flags.mangle || cli.flags.m || true,
module: cli.flags.module || false,
format: cli.flags.format || cli.flags.f || null,
sourceMap: cli.flags['source-map'] || false,
toplevel: cli.flags.toplevel || false,
nameCache: cli.flags['name-cache'] || null,
ie8: cli.flags.ie8 || false,
keep_classnames: cli.flags['keep-classnames'] || undefined,
keep_fnames: cli.flags['keep-fnames'] || false,
safari10: cli.flags.safari10 || false
})
)
);
const minifyResult: MinifyOutput = await minify(originalCode, defaultConfigs);

const fileDirectory = dirname(filePath);

if (!(await isFileExists(fileDirectory))) {
await mkdir(fileDirectory, { recursive: true });
}

if (minifyResult.code) {
await writeFile(newFilePath, minifyResult.code);
await writeFile(filePath, minifyResult.code);
}
}
}
Expand Down
File renamed without changes.

0 comments on commit b6c2ca5

Please sign in to comment.