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

[typescript] add typescript support for the server and browser #19104

Merged
merged 16 commits into from
May 18, 2018
Merged
Show file tree
Hide file tree
Changes from 13 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
22 changes: 21 additions & 1 deletion docs/development/plugin/development-plugin-resources.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,24 @@ The Kibana directory must be named `kibana`, and your plugin directory must be l
If you're developing a plugin that has a user interface, take a look at our https://elastic.github.io/eui[Elastic UI Framework].
It documents the CSS and React components we use to build Kibana's user interface.

You're welcome to use these components, but be aware that they are rapidly evolving and we might introduce breaking changes that will disrupt your plugin's UI.
You're welcome to use these components, but be aware that they are rapidly evolving and we might introduce breaking changes that will disrupt your plugin's UI.

[float]
==== TypeScript Support
Plugin code can be written in http://www.typescriptlang.org/[TypeScript] if desired. To enable TypeScript support create a `tsconfig.json` file at the root of your plugin that looks something like this:

["source","js"]
-----------
{
// extend Kibana's tsconfig, or use your own settings
"extends": "../../kibana/tsconfig.json",

// tell the TypeScript compiler where to find your source files
"include": [
"server/**/*",
"public/**/*"
]
}
-----------

TypeScript code is automatically converted into JavaScript during development, but not in the distributable version of Kibana. If you use the {repo}blob/{branch}/packages/kbn-plugin-helpers[@kbn/plugin-helpers] to build your plugin then your `.ts` and `.tsx` files will be permanently transpiled before your plugin is archived. If you have your own build process, make sure to run the TypeScript compiler on your source files and ship the compilation output so that your plugin will work with the distributable version of Kibana.
Copy link
Member

Choose a reason for hiding this comment

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

I remember we removed support for jsx, was it decided to bring tsx back in?

Copy link
Member

Choose a reason for hiding this comment

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

sorry, the .jsx extension, not jsx itself

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll let @spalger confirm, but the same question popped into my head when I was reviewing this. The ts docs make it seem like you must use .tsx for jsx in typescript. I didn't check if the alternative worked because I figured we should probably do what the project recommends regardless.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's not optional in TypeScript. JSX support is only available in .tsx files.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@
"validate-npm-package-name": "2.2.2",
"vega-lib": "^3.3.1",
"vega-lite": "^2.4.0",
"vega-tooltip": "^0.9.14",
"vega-schema-url-parser": "1.0.0",
"vega-tooltip": "^0.9.14",
"vision": "4.1.0",
"webpack": "3.6.0",
"webpack-merge": "4.1.0",
Expand All @@ -225,6 +225,7 @@
"@kbn/eslint-plugin-license-header": "link:packages/kbn-eslint-plugin-license-header",
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
"@kbn/test": "link:packages/kbn-test",
"@types/minimatch": "^2.0.29",
"angular-mocks": "1.4.7",
"babel-eslint": "8.1.2",
"babel-jest": "^22.4.3",
Expand Down Expand Up @@ -299,8 +300,10 @@
"supertest": "3.0.0",
"supertest-as-promised": "4.0.2",
"tree-kill": "^1.1.0",
"ts-jest": "^22.4.3",
"typescript": "^2.8.1",
"ts-jest": "^22.4.6",
"ts-loader": "^3.5.0",
"ts-node": "^6.0.3",
"typescript": "^2.8.3",
"vinyl-fs": "^3.0.2",
"xml2js": "^0.4.19",
"xmlbuilder": "9.0.4"
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-dev-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@kbn/dev-utils",
"main": "./target/index.js",
"types": "./index.d.ts",
"version": "1.0.0",
"license": "Apache-2.0",
"private": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ function tryNodeResolver(importRequest, file, config) {
return nodeResolver.resolve(
importRequest,
file,
// we use Object.assign so that this file is compatible with slightly older
// versions of node.js used by IDEs (eg. resolvers are run in the Electron
// process in Atom)
Object.assign({}, config, {
extensions: ['.js', '.json'],
extensions: ['.js', '.json', '.ts', '.tsx'],
isFile,
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ exports.getWebpackConfig = function(kibanaPath, projectRoot, config) {
return {
context: kibanaPath,
resolve: {
extensions: ['.js', '.json'],
extensions: ['.js', '.json', '.ts', '.tsx'],
mainFields: ['browser', 'main'],
modules: [
'webpackShims',
Expand Down
17 changes: 17 additions & 0 deletions packages/kbn-plugin-helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,20 @@ Setting | Description
`skipInstallDependencies` | Don't install dependencies defined in package.json into build output
`buildVersion` | Version for the build output
`kibanaVersion` | Kibana version for the build output (added to package.json)

## TypeScript support

Plugin code can be written in [TypeScript](http://www.typescriptlang.org/) if desired. To enable TypeScript support create a `tsconfig.json` file at the root of your plugin that looks something like this:

```js
{
// extend Kibana's tsconfig, or use your own settings
"extends": "../../kibana/tsconfig.json",

// tell the TypeScript compiler where to find your source files
"include": [
"server/**/*",
"public/**/*"
]
}
```
1 change: 1 addition & 0 deletions packages/kbn-plugin-helpers/lib/plugin_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = function(root) {

const buildSourcePatterns = [
'yarn.lock',
'tsconfig.json',
'package.json',
'index.js',
'{lib,public,server,webpackShims,translations}/**/*',
Expand Down
57 changes: 56 additions & 1 deletion packages/kbn-plugin-helpers/tasks/build/create_build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const join = require('path').join;
const relative = require('path').relative;
const unlinkSync = require('fs').unlinkSync;
const { readFileSync, writeFileSync, unlinkSync, existsSync } = require('fs');
const execFileSync = require('child_process').execFileSync;
const del = require('del');
const vfs = require('vinyl-fs');
Expand Down Expand Up @@ -29,6 +29,22 @@ function removeSymlinkDependencies(root) {
});
}

// parse a ts config file
function parseTsconfig(pluginSourcePath, configPath) {
const ts = require(join(pluginSourcePath, 'node_modules', 'typescript'));
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: There's no need to use path.join for require calls. This can just be done through template literal.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is Windows path support.

Copy link
Contributor

Choose a reason for hiding this comment

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

Node's require function should work in an OS agnostic way, that's why you don't need to bother with join when doing relative imports and whatnot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I kind of hate using template literals for paths, it's like a phobia of mine, kind of like using concatenation to join parts of a URL... Do you care if I leave join in here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't mind


const { error, config } = ts.parseConfigFileTextToJson(
configPath,
readFileSync(configPath, 'utf8')
);

if (error) {
throw error;
}

return config;
}

module.exports = function createBuild(
plugin,
buildTarget,
Expand Down Expand Up @@ -83,6 +99,45 @@ module.exports = function createBuild(
options
);
})
.then(function() {
const buildConfigPath = join(buildRoot, 'tsconfig.json');

if (!existsSync(buildConfigPath)) {
return;
}

if (!plugin.pkg.devDependencies.typescript) {
throw new Error(
'Found tsconfig.json file in plugin but typescript is not a devDependency.'
);
}

// attempt to patch the extends path in the tsconfig file
const buildConfig = parseTsconfig(buildSource, buildConfigPath);

if (buildConfig.extends) {
buildConfig.extends = join(
relative(buildRoot, buildSource),
buildConfig.extends
);

writeFileSync(buildConfigPath, JSON.stringify(buildConfig));
}

execFileSync(
join(buildSource, 'node_modules', '.bin', 'tsc'),
['--pretty', 'true'],
{
cwd: buildRoot,
stdio: ['ignore', 'pipe', 'pipe'],
}
);

del.sync([
join(buildRoot, '**', '*.{ts,tsx,d.ts}'),
join(buildRoot, 'tsconfig.json'),
]);
})
.then(function() {
const buildFiles = [relative(buildTarget, buildRoot) + '/**/*'];

Expand Down
6 changes: 6 additions & 0 deletions packages/kbn-pm/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ module.exports = {
},
{
loader: 'ts-loader',
options: {
compilerOptions: {
// enable esnext modules so webpack can do its thing better
module: 'esnext',
},
},
},
],
exclude: /node_modules/,
Expand Down
2 changes: 0 additions & 2 deletions packages/kbn-system-loader/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"declaration": true,
"outDir": "./target"
},
Expand Down
10 changes: 10 additions & 0 deletions src/babel-register/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
// unless we are running a prebuilt/distributable version of
// kibana, automatically transpile typescript to js before babel
if (!global.__BUILT_WITH_BABEL__) {
const { resolve } = require('path');
require('ts-node').register({
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: With this addition, I'm not sure if babel-register is the appropriate name for this module anymore. It's more of a runtime-compiler or something. I can live with it for now, but I figured I'd mention it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I thought that at first but then realized that once babel 7 is available it will really be babel-register again, so maybe it's fine that in the meantime it's a little more that just babel-register.

transpileOnly: true,
cacheDirectory: resolve(__dirname, '../../optimize/.cache/ts-node')
});
}

// register and polyfill need to happen in this
// order and in separate files. Checkout each file
// for a much more detailed explaination
Expand Down
8 changes: 6 additions & 2 deletions src/dev/build/build_distributables.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CleanExtraBinScriptsTask,
CleanExtraFilesFromModulesTask,
CleanPackagesTask,
CleanTypescriptTask,
CleanTask,
CopySourceTask,
CreateArchivesSourcesTask,
Expand All @@ -21,7 +22,8 @@ import {
InstallDependenciesTask,
OptimizeBuildTask,
RemovePackageJsonDepsTask,
TranspileSourceTask,
TranspileBabelTask,
TranspileTypescriptTask,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: TypeScript is the correct capitalization. Also, to be truly consistent, I'd expect a TranspileECMAScriptTask rather than TranspileBabelTask, since babel is the transpiler rather than the thing we're transpiling. Or maybe switch out TranspileTypescriptTask with TranspileTscTask?

I have no strong opinions here, as I think the current names get the intention across well enough even if their names aren't completely consistent.

Copy link
Contributor Author

@spalger spalger May 17, 2018

Choose a reason for hiding this comment

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

Yeah, I think TypeScript is correct in prose but IMO caps in camelCase should be based on words, so TypeScript would imply that the prose spelling is Type Script to me. I also think it makes more sense as typescript in snake_case, rather than type_script, for the same reason... So I'm going to stick with what I have.

UpdateLicenseFileTask,
VerifyEnvTask,
VerifyExistingNodeBuildsTask,
Expand Down Expand Up @@ -76,10 +78,12 @@ export async function buildDistributables(options) {
await run(CopySourceTask);
await run(CreateEmptyDirsAndFilesTask);
await run(CreateReadmeTask);
await run(TranspileSourceTask);
await run(TranspileBabelTask);
await run(TranspileTypescriptTask);
await run(BuildPackagesTask);
await run(CreatePackageJsonTask);
await run(InstallDependenciesTask);
await run(CleanTypescriptTask);
await run(CleanPackagesTask);
await run(CreateNoticeFileTask);
await run(UpdateLicenseFileTask);
Expand Down
24 changes: 24 additions & 0 deletions src/dev/build/lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import fs from 'fs';
import { createHash } from 'crypto';
import { resolve, dirname, isAbsolute } from 'path';
import { createGunzip } from 'zlib';
import { inspect } from 'util';

import vfs from 'vinyl-fs';
import { promisify } from 'bluebird';
import mkdirpCb from 'mkdirp';
import del from 'del';
import { createPromiseFromStreams, createMapStream } from '../../../utils';

import { Extract } from 'tar';
Expand All @@ -26,6 +28,12 @@ function assertAbsolute(path) {
}
}

function longInspect(value) {
return inspect(value, {
maxArrayLength: Infinity
});
}

export async function mkdirp(path) {
assertAbsolute(path);
await mkdirpAsync(path);
Expand Down Expand Up @@ -67,6 +75,22 @@ export async function copy(source, destination) {
await chmodAsync(destination, stat.mode);
}

export async function deleteAll(log, patterns) {
if (!Array.isArray(patterns)) {
throw new TypeError('Expected patterns to be an array');
}

log.debug('Deleting patterns:', longInspect(patterns));

for (const pattern of patterns) {
assertAbsolute(pattern.startsWith('!') ? pattern.slice(1) : pattern);
}

const files = await del(patterns);
log.debug('Deleted %d files/directories', files.length);
log.verbose('Deleted:', longInspect(files));
}

export async function copyAll(sourceDir, destination, options = {}) {
const {
select = ['**/*'],
Expand Down
1 change: 1 addition & 0 deletions src/dev/build/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export {
copyAll,
getFileHash,
untar,
deleteAll,
} from './fs';
38 changes: 29 additions & 9 deletions src/dev/build/tasks/clean_tasks.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import del from 'del';
import { deleteAll } from '../lib';

export const CleanTask = {
global: true,
description: 'Cleaning artifacts from previous builds',

async run(config) {
await del([
async run(config, log) {
await deleteAll(log, [
config.resolveFromRepo('build'),
config.resolveFromRepo('target'),
]);
Expand All @@ -16,15 +16,29 @@ export const CleanPackagesTask = {
description: 'Cleaning source for packages that are now installed in node_modules',

async run(config, log, build) {
await del([build.resolvePath('packages'), build.resolvePath('x-pack')]);
await deleteAll(log, [
build.resolvePath('packages'),
build.resolvePath('x-pack')
]);
},
};

export const CleanTypescriptTask = {
description: 'Cleaning typescript source files that have been transpiled to JS',

async run(config, log, build) {
await deleteAll(log, [
build.resolvePath('**/*.{ts,tsx,d.ts}'),
build.resolvePath('**/tsconfig.json'),
]);
},
};

export const CleanExtraFilesFromModulesTask = {
description: 'Cleaning tests, examples, docs, etc. from node_modules',

async run(config, log, build) {
await del([
await deleteAll(log, [
build.resolvePath('node_modules/**/test/**/*'),
build.resolvePath('node_modules/**/tests/**/*'),
build.resolvePath('node_modules/**/example/**/*'),
Expand All @@ -38,10 +52,16 @@ export const CleanExtraBinScriptsTask = {

async run(config, log, build) {
for (const platform of config.getPlatforms()) {
const patterns = platform.isWindows() ? ['*', '!*.bat'] : ['*.bat'];
await del(patterns, {
cwd: build.resolvePathForPlatform(platform, 'bin')
});
if (platform.isWindows()) {
await deleteAll(log, [
build.resolvePathForPlatform(platform, 'bin', '*'),
`!${build.resolvePathForPlatform(platform, 'bin', '*.bat')}`
]);
} else {
await deleteAll(log, [
build.resolvePathForPlatform(platform, 'bin', '*.bat'),
]);
}
}
}
};
Loading