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

Proposal: AST mode #887

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,13 @@ Ok, so how is that relevant to ts-loader? Because the best way to think about wh

**TypeScript version compatibility.** As a final caveat, [this commit to TypeScript](https://github.com/Microsoft/TypeScript/commit/d519e3f21ec36274726c44dab25c9eb48e34953f) is necessary for the `include` or `exclude` options of a project-referenced tsconfig file to work. It should be released in TypeScript 3.1.1 according to the tags. To use an earlier version of TypeScript, referenced project configuration files must specify `files`, and not `include`.

#### ast _(boolean) (default=false)_
Instead of transforming TypeScript into JavaScript, as you surely want to do if you’re looking at this loader, this option transforms TypeScript into a JSON representation of its [Abstract Syntax Tree](https://astexplorer.net/#/gist/96f5a3e5ab73bd5c003e49b9cfc749d2/606af49c74f9e05d511746a9480e1369628a4a8b) (AST). Potentially useful for autogenerating documentation of source files. The loader emits JSON, not a JavaScript module, so you’ll need to add `json-loader` in front of this loader. Typical usage is likely to be with Webpack’s inline loader syntax rather than in your Webpack config, since you’ll probably want to use TS loader as usual for most files:

```js
const ast = require('!json-loader!ts-loader?ast!./source-file.ts');
```

### Usage with webpack watch

Because TS will generate .js and .d.ts files, you should ignore these files, otherwise watchers may go into an infinite watch loop. For example, when using webpack, you may wish to add this to your webpack.conf.js file:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@
"dependencies": {
"chalk": "^2.3.0",
"enhanced-resolve": "^4.0.0",
"json-stringify-safe": "^5.0.1",
"loader-utils": "^1.0.2",
"micromatch": "^3.1.4",
"semver": "^5.0.1"
},
"devDependencies": {
"@types/json-stringify-safe": "^5.0.0",
"@types/micromatch": "^3.1.0",
"@types/node": "^10.0.0",
"@types/semver": "^5.4.0",
Expand All @@ -72,6 +74,7 @@
"html-webpack-plugin": "^3.2.0",
"husky": "^1.0.0",
"jasmine-core": "^3.0.0",
"json-loader": "^0.5.7",
"karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-jasmine": "^2.0.0",
Expand Down
29 changes: 26 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as jsonStringifySafe from 'json-stringify-safe';
import * as loaderUtils from 'loader-utils';
import * as path from 'path';
import * as typescript from 'typescript';
Expand All @@ -17,6 +18,7 @@ import {
import {
appendSuffixesIfMatch,
arrify,
ensureProgram,
formatErrors,
getAndCacheOutputJSFileName,
getAndCacheProjectReference,
Expand Down Expand Up @@ -72,7 +74,21 @@ function successLoader(

const fileVersion = updateFileInCache(filePath, contents, instance);
const referencedProject = getAndCacheProjectReference(filePath, instance);
if (referencedProject !== undefined) {
if (options.ast) {
const program = ensureProgram(instance);
if (!program) {
// Theoretically, these errors should only happen with `transpileOnly`,
// which is validated against while getting loader options.
throw new Error('TypeScript program could not be found.');
}
const sourceFile = program.getSourceFile(filePath);
if (!sourceFile) {
const relativePath = path.relative(loaderContext.rootContext, filePath);
throw new Error(`Source file '${relativePath}' could not be found.`);
}

callback(null, jsonStringifySafe(sourceFile, null, 2));
} else if (referencedProject !== undefined) {
const [relativeProjectConfigPath, relativeFilePath] = [
path.relative(
loaderContext.rootContext,
Expand Down Expand Up @@ -255,7 +271,8 @@ const validLoaderOptions: ValidLoaderOptions[] = [
'allowTsInNodeModules',
'experimentalFileCaching',
'projectReferences',
'resolveModuleName'
'resolveModuleName',
'ast'
];

/**
Expand Down Expand Up @@ -289,6 +306,11 @@ ${validLoaderOptions.join(' / ')}
}'.`
);
}
if (loaderOptions.ast && loaderOptions.transpileOnly) {
throw new Error(
`'transpileOnly' cannot be used in conjunction with 'ast'.`
);
}
}

function makeLoaderOptions(instanceName: string, loaderOptions: LoaderOptions) {
Expand All @@ -313,7 +335,8 @@ function makeLoaderOptions(instanceName: string, loaderOptions: LoaderOptions) {
// When the watch API usage stabilises look to remove this option and make watch usage the default behaviour when available
experimentalWatchApi: false,
allowTsInNodeModules: false,
experimentalFileCaching: true
experimentalFileCaching: true,
ast: false
} as Partial<LoaderOptions>,
loaderOptions
);
Expand Down
7 changes: 4 additions & 3 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ export type ResolveSync = (

export interface WatchHost
extends typescript.WatchCompilerHostOfFilesAndCompilerOptions<
typescript.BuilderProgram
> {
typescript.BuilderProgram
> {
invokeFileWatcher(
fileName: string,
eventKind: typescript.FileWatcherEventKind
Expand Down Expand Up @@ -299,7 +299,7 @@ export type ResolveModuleName = (
moduleName: string,
containingFile: string,
compilerOptions: typescript.CompilerOptions,
moduleResolutionHost: typescript.ModuleResolutionHost,
moduleResolutionHost: typescript.ModuleResolutionHost
) => typescript.ResolvedModuleWithFailedLookupLocations;

export type CustomResolveModuleName = (
Expand Down Expand Up @@ -336,6 +336,7 @@ export interface LoaderOptions {
experimentalFileCaching: boolean;
projectReferences: boolean;
resolveModuleName?: CustomResolveModuleName;
ast: boolean;
}

export interface TSFile {
Expand Down
2 changes: 1 addition & 1 deletion test/aliasLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = function aliasLoaderWithOptions(config, tsLoaderPath, options)
if (use.loader.indexOf('ts-loader') !== -1) {
use.loader = use.loader.replace('ts-loader', tsLoaderPath);
if (options) {
use.options = Object.assign({}, options, rule.options);
use.options = Object.assign({}, use.options, options, rule.options);
}
}
})
Expand Down
12 changes: 12 additions & 0 deletions test/comparison-tests/ast/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Example from https://basarat.gitbooks.io/typescript/docs/types/generics.html

/** A class definition with a generic parameter */
class Queue<T> {
private data = [];
public push = (item: T) => this.data.push(item);
public pop = (): T => this.data.shift();
}

/** Again sample usage */
const queue = new Queue<number>();
queue.push(0);
100 changes: 100 additions & 0 deletions test/comparison-tests/ast/expectedOutput-3.2/bundle.js

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions test/comparison-tests/ast/expectedOutput-3.2/bundle.transpiled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/******/ (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 = "./app.ts");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./app.ts":
/*!****************!*\
!*** ./app.ts ***!
\****************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("throw new Error(\"Module build failed (from /Users/anbranc/Developer/ts-loader/index.js):/nError: 'transpileOnly' cannot be used in conjunction with 'ast'./n at validateLoaderOptions (/Users/anbranc/Developer/ts-loader/dist/index.js:180:15)/n at getLoaderOptions (/Users/anbranc/Developer/ts-loader/dist/index.js:125:5)/n at Object.loader (/Users/anbranc/Developer/ts-loader/dist/index.js:17:21)\");\n\n//# sourceURL=webpack:///./app.ts?");

/***/ })

/******/ });
11 changes: 11 additions & 0 deletions test/comparison-tests/ast/expectedOutput-3.2/output.transpiled.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Asset Size Chunks Chunk Names
bundle.js 4.14 KiB main [emitted] main
Entrypoint main = bundle.js
[./app.ts] 406 bytes {main} [built] [failed] [1 error]

ERROR in ./app.ts
Module build failed (from index.js):
Error: 'transpileOnly' cannot be used in conjunction with 'ast'.
at validateLoaderOptions (dist/index.js:180:15)
at getLoaderOptions (dist/index.js:125:5)
at Object.loader (dist/index.js:17:21)
4 changes: 4 additions & 0 deletions test/comparison-tests/ast/expectedOutput-3.2/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Asset Size Chunks Chunk Names
bundle.js 56.1 KiB main [emitted] main
Entrypoint main = bundle.js
[./app.ts] 47.4 KiB {main} [built]
5 changes: 5 additions & 0 deletions test/comparison-tests/ast/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files": [
"app.ts"
]
}
30 changes: 30 additions & 0 deletions test/comparison-tests/ast/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
var path = require('path')

module.exports = {
mode: 'development',
entry: './app.ts',
output: {
filename: 'bundle.js'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'json-loader'
},
{
loader: 'ts-loader',
options: { ast: true }
}
]
}
]
}
}


4 changes: 3 additions & 1 deletion test/comparison-tests/create-and-execute-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,9 @@ function getNormalisedFileContent(file, location) {
.replace(/!\** (C\:\/)?[\w|\/|-]*\/comparison-tests\//g, '!*** /ts-loader/test/comparison-tests/')
.replace(/\/ (C\:\/)?[\w|\/|-]*\/comparison-tests\//g, '/ /ts-loader/test/comparison-tests/')
// with webpack 4 there are different numbers of *s on Windows and on Linux
.replace(/\*{10}\**/g, '**********');
.replace(/\*{10}\**/g, '**********')
// AST test has absolute paths in the AST
.replace(/"c:\/ast\/app\.ts\/"/ig, '"/ast/app.ts"');
} catch (e) {
fileContent = '!!!' + filePath + ' doesn\'t exist!!!';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
/*! no static exports found */
/***/ (function(module, exports) {

eval("throw new Error(\"Module build failed (from C:/source/ts-loader/index.js):/nError: ts-loader was supplied with an unexpected loader option: notRealOption/n/nPlease take a look at the options you are supplying; the following are valid options:/nsilent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName/n/n at validateLoaderOptions (C://source//ts-loader//dist//index.js:152:19)/n at getLoaderOptions (C://source//ts-loader//dist//index.js:110:5)/n at Object.loader (C://source//ts-loader//dist//index.js:16:21)\");\n\n//# sourceURL=webpack:///./app.ts?");
eval("throw new Error(\"Module build failed (from /Users/anbranc/Developer/ts-loader/index.js):/nError: ts-loader was supplied with an unexpected loader option: notRealOption/n/nPlease take a look at the options you are supplying; the following are valid options:/nsilent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName / ast/n/n at validateLoaderOptions (/Users/anbranc/Developer/ts-loader/dist/index.js:168:19)/n at getLoaderOptions (/Users/anbranc/Developer/ts-loader/dist/index.js:125:5)/n at Object.loader (/Users/anbranc/Developer/ts-loader/dist/index.js:17:21)\");\n\n//# sourceURL=webpack:///./app.ts?");

/***/ })

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Asset Size Chunks Chunk Names
bundle.js 4.6 KiB main [emitted] main
Asset Size Chunks Chunk Names
bundle.js 4.63 KiB main [emitted] main
Entrypoint main = bundle.js
[./app.ts] 855 bytes {main} [built] [failed] [1 error]
[./app.ts] 909 bytes {main} [built] [failed] [1 error]

ERROR in ./app.ts
Module build failed (from /index.js):
Module build failed (from index.js):
Error: ts-loader was supplied with an unexpected loader option: notRealOption

Please take a look at the options you are supplying; the following are valid options:
silent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName
silent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName / ast

at validateLoaderOptions (dist\index.js:152:19)
at getLoaderOptions (dist\index.js:110:5)
at Object.loader (dist\index.js:16:21)
at validateLoaderOptions (dist/index.js:168:19)
at getLoaderOptions (dist/index.js:125:5)
at Object.loader (dist/index.js:17:21)
Loading