Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add --tsconfig-path option for CLI & support extends property in tsconfig.json #914

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"node-gyp": "^8.4.1",
"npm": "^6.13.4",
"oracledb": "^4.2.0",
"param-case": "^3.0.4",
"passport": "^0.5.2",
"passport-google-oauth": "^2.0.0",
"path-platform": "^0.11.15",
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Outputs the Node.js compact build of `input.js` into `dist/index.js`.
--license [file] Adds a file containing licensing information to the output
--stats-out [file] Emit webpack stats as json to the specified output file
--target [es] ECMAScript target to use for output (default: es2015)
--tsconfig-path [file] Specify tsconfig.json to use for build (default: resolve tsconfig.json from entrypoint)
Learn more: https://webpack.js.org/configuration/target
-d, --debug Show debug logs
```
Expand Down
7 changes: 5 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Options:
--license [file] Adds a file containing licensing information to the output
--stats-out [file] Emit webpack stats as json to the specified output file
--target [es] ECMAScript target to use for output (default: es2015)
--tsconfig-path [file] Specify tsconfig.json to use for build (default: resolve tsconfig.json from entrypoint)
Learn more: https://webpack.js.org/configuration/target
-d, --debug Show debug logs
`;
Expand Down Expand Up @@ -154,7 +155,8 @@ async function runCmd (argv, stdout, stderr) {
"-t": "--transpile-only",
"--license": String,
"--stats-out": String,
"--target": String
"--target": String,
"--tsconfig-path": String
}, {
permissive: false,
argv
Expand Down Expand Up @@ -250,7 +252,8 @@ async function runCmd (argv, stdout, stderr) {
transpileOnly: args["--transpile-only"],
license: args["--license"],
quiet,
target: args["--target"]
target: args["--target"],
tsconfigPath: args["--tsconfig-path"]
}
);

Expand Down
54 changes: 14 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ const webpack = require("webpack");
const MemoryFS = require("memory-fs");
const terser = require("terser");
const tsconfigPaths = require("tsconfig-paths");
const { loadTsconfig } = require("tsconfig-paths/lib/tsconfig-loader");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const shebangRegEx = require('./utils/shebang');
const nccCacheDir = require("./utils/ncc-cache-dir");
const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin;
const { version: nccVersion } = require('../package.json');
const { hasTypeModule } = require('./utils/has-type-module');
const { loadTsconfigOptions } = require('./utils/load-tsconfig-options');

// support glob graceful-fs
fs.gracefulify(require("fs"));
Expand Down Expand Up @@ -57,7 +57,8 @@ function ncc (
production = true,
// webpack defaults to `module` and `main`, but that's
// not really what node.js supports, so we reset it
mainFields = ['main']
mainFields = ['main'],
tsconfigPath = undefined
} = {}
) {
// v8 cache not supported for ES modules
Expand Down Expand Up @@ -108,23 +109,18 @@ function ncc (
existingAssetNames.push(`${filename}.cache`);
existingAssetNames.push(`${filename}.cache${ext}`);
}
const compilerOptions = loadTsconfigOptions(tsconfigPath, {
base: process.cwd(),
start: dirname(entry),
filename: 'tsconfig.json'
});
const resolvePlugins = [];
// add TsconfigPathsPlugin to support `paths` resolution in tsconfig
// we need to catch here because the plugin will
// error if there's no tsconfig in the working directory
let fullTsconfig = {};
try {
const configFileAbsolutePath = walkParentDirs({
base: process.cwd(),
start: dirname(entry),
filename: 'tsconfig.json',
});
fullTsconfig = loadTsconfig(configFileAbsolutePath) || {
compilerOptions: {}
};

const tsconfigPathsOptions = { silent: true }
if (fullTsconfig.compilerOptions.allowJs) {
if (compilerOptions.allowJs) {
tsconfigPathsOptions.extensions = SUPPORTED_EXTENSIONS
}
resolvePlugins.push(new TsconfigPathsPlugin(tsconfigPathsOptions));
Expand Down Expand Up @@ -361,7 +357,7 @@ function ncc (
compilerOptions: {
module: 'esnext',
target: 'esnext',
...fullTsconfig.compilerOptions,
...compilerOptions,
allowSyntheticDefaultImports: true,
noEmit: false,
outDir: '//'
Expand Down Expand Up @@ -452,7 +448,7 @@ function ncc (

async function finalizeHandler (stats) {
const assets = Object.create(null);
getFlatFiles(mfs.data, assets, relocateLoader.getAssetMeta, fullTsconfig);
getFlatFiles(mfs.data, assets, relocateLoader.getAssetMeta, compilerOptions);
// filter symlinks to existing assets
const symlinks = Object.create(null);
for (const [key, value] of Object.entries(relocateLoader.getSymlinks())) {
Expand Down Expand Up @@ -646,17 +642,17 @@ function ncc (
}

// this could be rewritten with actual FS apis / globs, but this is simpler
function getFlatFiles(mfsData, output, getAssetMeta, tsconfig, curBase = "") {
function getFlatFiles(mfsData, output, getAssetMeta, tsconfigCompilerOptions, curBase = "") {
for (const path of Object.keys(mfsData)) {
const item = mfsData[path];
let curPath = `${curBase}/${path}`;
// directory
if (item[""] === true) getFlatFiles(item, output, getAssetMeta, tsconfig, curPath);
if (item[""] === true) getFlatFiles(item, output, getAssetMeta, tsconfigCompilerOptions, curPath);
// file
else if (!curPath.endsWith("/")) {
const meta = getAssetMeta(curPath.slice(1)) || {};
if(curPath.endsWith(".d.ts")) {
const outDir = tsconfig.compilerOptions.outDir ? pathResolve(tsconfig.compilerOptions.outDir) : pathResolve('dist');
const outDir = tsconfigCompilerOptions.outDir ? pathResolve(tsconfigCompilerOptions.outDir) : pathResolve('dist');
curPath = curPath
.replace(outDir, "")
.replace(process.cwd(), "")
Expand All @@ -668,25 +664,3 @@ function getFlatFiles(mfsData, output, getAssetMeta, tsconfig, curBase = "") {
}
}
}

// Adapted from https://github.com/vercel/vercel/blob/18bec983aefbe2a77bd14eda6fca59ff7e956d8b/packages/build-utils/src/fs/run-user-scripts.ts#L289-L310
function walkParentDirs({
base,
start,
filename,
}) {
let parent = '';

for (let current = start; base.length <= current.length; current = parent) {
const fullPath = join(current, filename);

// eslint-disable-next-line no-await-in-loop
if (fs.existsSync(fullPath)) {
return fullPath;
}

parent = dirname(current);
}

return null;
}
137 changes: 137 additions & 0 deletions src/utils/load-tsconfig-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const ts = require('typescript');
const { join, dirname, resolve } = require('path');
const fs = require('fs');
const { paramCase } = require('param-case');

/**
* @typedef {object} LoadTsconfigInit
* @property {string} base
* @property {string} start
* @property {string} filename
*/

/**
* @description Adapted from https://github.com/vercel/vercel/blob/18bec983aefbe2a77bd14eda6fca59ff7e956d8b/packages/build-utils/src/fs/run-user-scripts.ts#L289-L310
* @param {LoadTsconfigInit}
* @returns {string | null}
*/
function walkParentDirs({ base, start, filename }) {
let parent = '';

for (let current = start; base.length <= current.length; current = parent) {
const fullPath = join(current, filename);

if (fs.existsSync(fullPath)) {
return fullPath;
}

parent = dirname(current);
}

return null;
}

/**
* @param {ts.CompilerOptions} options
* @param {string | undefined} key
* @param {(value: string) => string} [callback]
* @returns {string | undefined}
*/
function convertEnumCompilerOptions(enumCompilerOptions, key, callback) {
if (key == null) {
return undefined;
}
const value = enumCompilerOptions[key];
return typeof callback === 'function' ? callback(value) : value;
}

/**
* @param {string} value
* @returns {string}
*/
function toLowerCase(value) {
return value.toLowerCase();
}

/**
* @param {ts.NewLineKind} newLine
* @returns {string | undefined}
*/
function normalizeNewLineOption(newLine) {
switch (newLine) {
case ts.NewLineKind.CarriageReturnLineFeed:
return 'crlf';
case ts.NewLineKind.LineFeed:
return 'lf';
default:
return undefined;
}
}

/**
* @param {ts.ModuleResolutionKind} moduleResolution
* @returns {string | undefined}
*/
function normalizeModuleResolutionOption(moduleResolution) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem very future proof.

Is there a way to have TypeScript convert this for us?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@styfle
I see.
I cloudn't find a way to get complete config objects by TypeScript, but I found tsconfck from microsoft/TypeScript#44573.

Does this library look future proof, do you think? If you think it's OK, I will rewrite src/utils/load-tsconfig-options.js.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrited this & related parts by 5d85cd2, which uses tsconfck.
Would be glad to review it.

switch (moduleResolution) {
case ts.ModuleResolutionKind.Classic:
return 'classic';
case ts.ModuleResolutionKind.NodeJs:
return 'node';
case ts.ModuleResolutionKind.Node12:
return 'node12';
case ts.ModuleResolutionKind.NodeNext:
return 'nodenext';
default:
return undefined;
}
}

/**
* @param {ts.CompilerOptions} options
* @returns {ts.CompilerOptions}
*/
function normalizeCompilerOptions(options) {
if (options.importsNotUsedAsValues != null) {
options.importsNotUsedAsValues = convertEnumCompilerOptions(
ts.ImportsNotUsedAsValues,
options.importsNotUsedAsValues,
toLowerCase,
);
}
if (options.jsx != null) {
options.jsx = convertEnumCompilerOptions(ts.JsxEmit, options.jsx, paramCase);
}
if (options.module != null) {
options.module = convertEnumCompilerOptions(ts.ModuleKind, options.module, toLowerCase);
}
if (options.moduleResolution != null) {
options.moduleResolution = normalizeModuleResolutionOption(options.moduleResolution);
}
if (options.newLine != null) {
options.newLine = normalizeNewLineOption(options.newLine);
}
if (options.target != null) {
options.target = convertEnumCompilerOptions(ts.ScriptTarget, options.target, toLowerCase);
}
return options;
}

/**
* @param {string | undefined} configPath
* @param {LoadTsconfigInit}
* @returns {ts.CompilerOptions}
*/
exports.loadTsconfigOptions = function (configPath, { base, start, filename }) {
// throw error if `configPath` does not exist
const tsconfig = configPath != null ? resolve(configPath) : walkParentDirs({ base, start, filename });
if (tsconfig == null) {
return {};
}
const content = ts.readConfigFile(tsconfig, ts.sys.readFile);
if (content.error != null || content.config == null) {
return {};
}
const { options } = ts.parseJsonConfigFileContent(content.config, ts.sys, dirname(tsconfig));
return normalizeCompilerOptions(options);
};
10 changes: 10 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ module.exports = [
args: ["run", "-t", "test/fixtures/with-type-errors/ts-error.ts"],
expect: { code: 0 }
},
{
args: ["run", "test/fixtures/ts-extends/any-args.ts"],
expect (code, stdout, stderr) {
return code === 1 && stderr.toString().indexOf('any-args.ts(4,20)') !== -1 && stderr.toString().indexOf('TS7006') !== -1;
}
},
{
args: ["run", "test/fixtures/ts-extends/any-args.ts", "--tsconfig-path", "test/fixtures/ts-extends/tsconfig.build.json"],
expect: { code: 0 }
},
{
args: ["build", "-o", "tmp", "test/fixtures/test.cjs"],
expect (code, stdout, stderr) {
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/ts-extends/any-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* throws error (TS7006: an implicit 'any' type) if strict options is set to true, or otherwise passes compilation
*/
function something(args) {
return args;
}
6 changes: 6 additions & 0 deletions test/fixtures/ts-extends/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"strict": false
}
}
5 changes: 5 additions & 0 deletions test/fixtures/ts-extends/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"strict": true
}
}
Loading