Skip to content

Commit

Permalink
Revert "Revert "Add support for tsconfig paths. (fixes #150)" (#171)…
Browse files Browse the repository at this point in the history
…" (#173)

This reverts commit d26fce1.
  • Loading branch information
rauchg committed Dec 17, 2018
1 parent d26fce1 commit 6aa2c4c
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -7,6 +7,9 @@ dist/**/*.js
!test/integration
!test/integration/*.json
!test/integration/*.js
!test/integration/*.ts
!test/unit
!test/unit/**
!dist/
!dist/ncc/
!dist/buildin/
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -87,6 +87,8 @@
"terser": "^3.11.0",
"the-answer": "^1.0.0",
"ts-loader": "^5.3.1",
"tsconfig-paths": "^3.7.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"twilio": "^3.23.2",
"typescript": "^3.2.2",
"vue": "^2.5.17",
Expand Down
46 changes: 42 additions & 4 deletions src/index.js
Expand Up @@ -5,6 +5,8 @@ const MemoryFS = require("memory-fs");
const WebpackParser = require("webpack/lib/Parser");
const webpackParse = WebpackParser.parse;
const terser = require("terser");
const tsconfigPaths = require("tsconfig-paths");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const shebangRegEx = require('./utils/shebang');

// overload the webpack parser so that we can make
Expand All @@ -21,7 +23,7 @@ WebpackParser.parse = function(source, opts = {}) {

const SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".json", ".node"];

function resolveModule(context, request, callback, forcedExternals = []) {
function resolveModule(matchPath, context, request, callback, forcedExternals = []) {
const resolveOptions = {
basedir: context,
preserveSymlinks: true,
Expand All @@ -35,6 +37,12 @@ function resolveModule(context, request, callback, forcedExternals = []) {

resolve(request, resolveOptions, err => {
if (err) {
// check tsconfig paths before erroring
if (matchPath && matchPath(request, undefined, undefined, SUPPORTED_EXTENSIONS)) {
callback();
return;
}

console.error(
`ncc: Module directory "${context}" attempted to require "${request}" but could not be resolved, assuming external.`
);
Expand All @@ -58,6 +66,20 @@ module.exports = async (
const mfs = new MemoryFS();
const assetNames = Object.create(null);
const assets = Object.create(null);
const resolvePlugins = [];
let tsconfigMatchPath;
// 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
try {
resolvePlugins.push(new TsconfigPathsPlugin({ silent: true }));

const tsconfig = tsconfigPaths.loadConfig();
if (tsconfig.resultType === "success") {
tsconfigMatchPath = tsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
}
} catch (e) {}

const compiler = webpack({
entry,
optimization: {
Expand All @@ -76,11 +98,12 @@ module.exports = async (
extensions: SUPPORTED_EXTENSIONS,
// webpack defaults to `module` and `main`, but that's
// not really what node.js supports, so we reset it
mainFields: ["main"]
mainFields: ["main"],
plugins: resolvePlugins
},
// https://github.com/zeit/ncc/pull/29#pullrequestreview-177152175
node: false,
externals: (...args) => resolveModule(...[...args, externals]),
externals: (...args) => resolveModule(tsconfigMatchPath, ...[...args, externals]),
module: {
rules: [
{
Expand Down Expand Up @@ -140,7 +163,7 @@ module.exports = async (
"var e = new Error",
`if (typeof req === 'number' && __webpack_require__.m[req])\n` +
` return __webpack_require__(req);\n` +
`try { return require(req) }\n` +
`try { return require(req) }\n` +
`catch (e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }\n` +
`var e = new Error`
);
Expand Down Expand Up @@ -176,6 +199,21 @@ module.exports = async (
});
compiler.inputFileSystem = fs;
compiler.outputFileSystem = mfs;
// tsconfig-paths-webpack-plugin requires a readJson method on the filesystem
compiler.inputFileSystem.readJson = (path, callback) => {
compiler.inputFileSystem.readFile(path, (err, data) => {
if (err) {
callback(err);
return;
}

try {
callback(null, JSON.parse(data));
} catch (e) {
callback(e);
}
});
};
compiler.resolvers.normal.fileSystem = mfs;
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
Expand Down
12 changes: 9 additions & 3 deletions test/index.test.js
Expand Up @@ -6,13 +6,19 @@ const { dirname } = require("path");

for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
it(`should generate correct output for ${unitTest}`, async () => {
const testDir = `${__dirname}/unit/${unitTest}`;
const expected = fs
.readFileSync(`${__dirname}/unit/${unitTest}/output.js`)
.readFileSync(`${testDir}/output.js`)
.toString()
.trim()
// Windows support
.replace(/\r/g, "");
await ncc(`${__dirname}/unit/${unitTest}/input.js`, { minify: false }).then(

// set env variable so tsconfig-paths can find the config
process.env.TS_NODE_PROJECT = `${testDir}/tsconfig.json`;
// find the name of the input file (e.g input.ts)
const inputFile = fs.readdirSync(testDir).find(file => file.includes("input"));
await ncc(`${testDir}/${inputFile}`, { minify: false }).then(
async ({ code, assets }) => {
// very simple asset validation in unit tests
if (unitTest.startsWith("asset-")) {
Expand All @@ -27,7 +33,7 @@ for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
expect(actual).toBe(expected);
} catch (e) {
// useful for updating fixtures
fs.writeFileSync(`${__dirname}/unit/${unitTest}/actual.js`, actual);
fs.writeFileSync(`${testDir}/actual.js`, actual);
throw e;
}
}
Expand Down
6 changes: 6 additions & 0 deletions test/unit/tsconfig-paths-conflicting-external/input.ts
@@ -0,0 +1,6 @@
import module from "@module";
// this matches the pattern specified in tsconfig,
// but it should use the external module instead of erroring
import * as _ from "@sentry/node";

console.log(module);
1 change: 1 addition & 0 deletions test/unit/tsconfig-paths-conflicting-external/module.ts
@@ -0,0 +1 @@
export default {};
110 changes: 110 additions & 0 deletions test/unit/tsconfig-paths-conflicting-external/output.js
@@ -0,0 +1,110 @@
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

exports.__esModule = true;
var _module_1 = __webpack_require__(1);
console.log(_module_1["default"]);


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

exports.__esModule = true;
exports["default"] = {};


/***/ })
/******/ ]);
10 changes: 10 additions & 0 deletions test/unit/tsconfig-paths-conflicting-external/tsconfig.json
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@*": ["./*"]
}
}
}
3 changes: 3 additions & 0 deletions test/unit/tsconfig-paths/input.ts
@@ -0,0 +1,3 @@
import module from "@module";

console.log(module);
1 change: 1 addition & 0 deletions test/unit/tsconfig-paths/module.ts
@@ -0,0 +1 @@
export default {};
110 changes: 110 additions & 0 deletions test/unit/tsconfig-paths/output.js
@@ -0,0 +1,110 @@
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

exports.__esModule = true;
var _module_1 = __webpack_require__(1);
console.log(_module_1["default"]);


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

exports.__esModule = true;
exports["default"] = {};


/***/ })
/******/ ]);

0 comments on commit 6aa2c4c

Please sign in to comment.