Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
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