diff --git a/package.json b/package.json index 16c57e12..ffc871fe 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "the-answer": "^1.0.0", "tiny-json-http": "^7.0.2", "ts-loader": "^9.3.0", + "tsconfck": "^1.2.2", "tsconfig-paths": "^3.7.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "twilio": "^3.23.2", diff --git a/readme.md b/readme.md index f3485a8d..c42f958c 100644 --- a/readme.md +++ b/readme.md @@ -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 ``` diff --git a/src/cli.js b/src/cli.js index eeeaebb7..9bbbf7de 100755 --- a/src/cli.js +++ b/src/cli.js @@ -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 `; @@ -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 @@ -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"] } ); @@ -343,8 +346,9 @@ async function runCmd (argv, stdout, stderr) { } } if (args["--watch"]) { - ncc.handler(handler); - ncc.rebuild(() => { + const nccWithWatchOption = await ncc + nccWithWatchOption.handler(handler); + nccWithWatchOption.rebuild(() => { if (ps) ps.kill(); startTime = Date.now(); diff --git a/src/index.js b/src/index.js index 593ea088..c3bf8af7 100644 --- a/src/index.js +++ b/src/index.js @@ -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")); @@ -32,7 +32,7 @@ const defaultPermissions = 0o666; const relocateLoader = eval('require(__dirname + "/loaders/relocate-loader.js")'); module.exports = ncc; -function ncc ( +async function ncc ( entry, { cache, @@ -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 @@ -108,21 +109,16 @@ function ncc ( existingAssetNames.push(`${filename}.cache`); existingAssetNames.push(`${filename}.cache${ext}`); } + const fullTsconfig = await 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) { tsconfigPathsOptions.extensions = SUPPORTED_EXTENSIONS @@ -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; -} diff --git a/src/utils/load-tsconfig-options.js b/src/utils/load-tsconfig-options.js new file mode 100644 index 00000000..e7a997b2 --- /dev/null +++ b/src/utils/load-tsconfig-options.js @@ -0,0 +1,55 @@ +const { join, dirname, resolve } = require('path'); +const fs = require('fs'); +const { parse } = require('tsconfck'); + +const DEFAULT_TSCONFIG_OPTIONS = { + compilerOptions: {} +}; + +/** + * @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 {string | undefined} configPath + * @param {LoadTsconfigInit} + * @returns {Promise} + */ +exports.loadTsconfigOptions = async 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 DEFAULT_TSCONFIG_OPTIONS; + } + try { + const result = await parse(tsconfig); + return result.tsconfig; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/test/cli.js b/test/cli.js index 8df03fda..c29d6ff7 100644 --- a/test/cli.js +++ b/test/cli.js @@ -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) { diff --git a/test/fixtures/ts-extends/any-args.ts b/test/fixtures/ts-extends/any-args.ts new file mode 100644 index 00000000..c9dcb73f --- /dev/null +++ b/test/fixtures/ts-extends/any-args.ts @@ -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; +} diff --git a/test/fixtures/ts-extends/tsconfig.build.json b/test/fixtures/ts-extends/tsconfig.build.json new file mode 100644 index 00000000..5787af45 --- /dev/null +++ b/test/fixtures/ts-extends/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "strict": false + } +} diff --git a/test/fixtures/ts-extends/tsconfig.json b/test/fixtures/ts-extends/tsconfig.json new file mode 100644 index 00000000..aee0ec94 --- /dev/null +++ b/test/fixtures/ts-extends/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strict": true + } +} diff --git a/yarn.lock b/yarn.lock index 680a6111..d8284b8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14854,6 +14854,11 @@ ts-loader@^9.3.0: micromatch "^4.0.0" semver "^7.3.4" +tsconfck@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-1.2.2.tgz#3f7ac55bcd16b8d89ff05ba8e27057f3375828c8" + integrity sha512-x5YpjOqjJnMs1EsJvQBQbrysrY32eGoZRRr5YvbN1hwlrXKc7jiphCOUrT7xbFdOWk8sh+EtMYbGPbTO8rDmcw== + tsconfig-paths-webpack-plugin@^3.2.0: version "3.5.1" resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.1.tgz#e4dbf492a20dca9caab60086ddacb703afc2b726"