Skip to content

Commit 5b35678

Browse files
committed
Add typescript plugin
1 parent 143b8f4 commit 5b35678

File tree

8 files changed

+215
-16
lines changed

8 files changed

+215
-16
lines changed

packages/react-dev-utils/plugins.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const getPackageJson = require('read-pkg-up').sync;
2020
const { dirname, isAbsolute } = require('path');
2121
const semver = require('semver');
2222

23-
function applyPlugins(config, plugins, { paths }) {
23+
function applyPlugins(config, plugins, { path, paths }) {
2424
const pluginPaths = plugins
2525
.map(p => {
2626
try {
@@ -32,11 +32,20 @@ function applyPlugins(config, plugins, { paths }) {
3232
.filter(e => e != null);
3333
for (const pluginPath of pluginPaths) {
3434
const { apply } = require(pluginPath);
35-
config = apply(config, { paths });
35+
config = apply(config, { path, paths });
3636
}
3737
return config;
3838
}
3939

40+
function hasPlugin(plugin) {
41+
try {
42+
require.resolve(`react-scripts-plugin-${plugin}`);
43+
return true;
44+
} catch (e) {
45+
return false;
46+
}
47+
}
48+
4049
function _getArrayValues(arr) {
4150
const { elements } = arr;
4251
return elements.map(e => {
@@ -256,6 +265,7 @@ function ejectFile({ filename, code, existingDependencies }) {
256265

257266
module.exports = {
258267
applyPlugins,
268+
hasPlugin,
259269
pushExtensions,
260270
pushExclusiveLoader,
261271
ejectFile,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "react-scripts-plugin-typescript",
3+
"version": "0.1.0",
4+
"description": "A plugin for react-scripts which enables TypeScript support.",
5+
"main": "src/index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"react-scripts",
11+
"typescript",
12+
"cra",
13+
"create",
14+
"react",
15+
"app",
16+
"plugin"
17+
],
18+
"license": "BSD-3-Clause",
19+
"dependencies": {
20+
"awesome-typescript-loader": "^3.2.1",
21+
"tsconfig-react-app": "^1.0.0",
22+
"typescript": "^2.4.1"
23+
},
24+
"devDependencies": {
25+
"react-dev-utils": "^3.0.2"
26+
},
27+
"peerDependencies": {
28+
"react-dev-utils": "^3.0.2"
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const {
4+
pushExtensions,
5+
pushExclusiveLoader,
6+
} = require('react-dev-utils/plugins');
7+
8+
function apply(config, { path, paths }) {
9+
pushExtensions({ config }, [['.js', '.tsx', '.ts']]);
10+
pushExclusiveLoader({ config }, '/\\.(js|jsx)$/', {
11+
// Process TypeScript with `at-loader`
12+
test: /\.(ts|tsx)$/,
13+
include: paths.appSrc,
14+
loader: require.resolve('awesome-typescript-loader'),
15+
options: {
16+
silent: true,
17+
forceConsistentCasingInFileNames: true,
18+
module: 'esnext',
19+
moduleResolution: 'node',
20+
downlevelIteration: true,
21+
sourceMap: true,
22+
target: 'es5',
23+
// @remove-on-eject-begin
24+
configFileName: path.join(paths.appSrc, 'tsconfig.json'),
25+
// @remove-on-eject-end
26+
},
27+
});
28+
return config;
29+
}
30+
31+
function eject() {
32+
// TODO: remove defaults above and inject into their file
33+
}
34+
35+
module.exports = { apply, eject, tsc: require('typescript') };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// @remove-on-eject-begin
2+
// @remove-file-on-eject typescript
3+
/**
4+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree. An additional grant
8+
* of patent rights can be found in the PATENTS file in the same directory.
9+
*/
10+
// @remove-on-eject-end
11+
'use strict';
12+
13+
const fs = require('fs');
14+
const crypto = require('crypto');
15+
const path = require('path');
16+
const paths = require('../paths');
17+
const tsc = (function() {
18+
try {
19+
return require('react-scripts-plugin-typescript').tsc;
20+
} catch (e) {
21+
return require('typescript');
22+
}
23+
})();
24+
const THIS_FILE = fs.readFileSync(__filename);
25+
26+
const strictCompilerConfig = {
27+
module: tsc.ModuleKind.CommonJS,
28+
};
29+
30+
let compilerConfig = Object.assign({}, strictCompilerConfig, {
31+
jsx: tsc.JsxEmit.React,
32+
});
33+
34+
const tsconfigPath = path.join(paths.appSrc, 'tsconfig.json');
35+
36+
if (fs.existsSync(tsconfigPath)) {
37+
try {
38+
const tsconfig = tsc.readConfigFile(tsconfigPath).config;
39+
40+
if (tsconfig && tsconfig.compilerOptions) {
41+
compilerConfig = Object.assign(
42+
{},
43+
tsconfig.compilerOptions,
44+
strictCompilerConfig
45+
);
46+
}
47+
} catch (e) {
48+
/* Do nothing - default is set */
49+
}
50+
}
51+
52+
module.exports = {
53+
process(src, path, config, options) {
54+
if (path.endsWith('.ts') || path.endsWith('.tsx')) {
55+
let compilerOptions = compilerConfig;
56+
if (options.instrument) {
57+
// inline source with source map for remapping coverage
58+
compilerOptions = Object.assign({}, compilerConfig);
59+
delete compilerOptions.sourceMap;
60+
compilerOptions.inlineSourceMap = true;
61+
compilerOptions.inlineSources = true;
62+
delete compilerOptions.outDir;
63+
}
64+
65+
const tsTranspiled = tsc.transpileModule(src, {
66+
compilerOptions: compilerOptions,
67+
fileName: path,
68+
});
69+
return tsTranspiled.outputText;
70+
}
71+
return src;
72+
},
73+
getCacheKey(fileData, filePath, configStr, options) {
74+
return crypto
75+
.createHash('md5')
76+
.update(THIS_FILE)
77+
.update('\0', 'utf8')
78+
.update(fileData)
79+
.update('\0', 'utf8')
80+
.update(filePath)
81+
.update('\0', 'utf8')
82+
.update(configStr)
83+
.update('\0', 'utf8')
84+
.update(JSON.stringify(compilerConfig))
85+
.update('\0', 'utf8')
86+
.update(options.instrument ? 'instrument' : '')
87+
.digest('hex');
88+
},
89+
};

packages/react-scripts/config/webpack.config.dev.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,4 @@ const base = {
293293
},
294294
};
295295

296-
module.exports = applyPlugins(base, [], { paths });
296+
module.exports = applyPlugins(base, ['typescript'], { path, paths });

packages/react-scripts/config/webpack.config.prod.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,4 @@ const base = {
361361
},
362362
};
363363

364-
module.exports = applyPlugins(base, [], { paths });
364+
module.exports = applyPlugins(base, ['typescript'], { path, paths });

packages/react-scripts/scripts/eject.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const paths = require('../config/paths');
2424
const createJestConfig = require('./utils/createJestConfig');
2525
const inquirer = require('react-dev-utils/inquirer');
2626
const spawnSync = require('react-dev-utils/crossSpawn').sync;
27-
const { ejectFile } = require('react-dev-utils/plugins');
27+
const { ejectFile, hasPlugin } = require('react-dev-utils/plugins');
2828

2929
const green = chalk.green;
3030
const cyan = chalk.cyan;
@@ -152,7 +152,12 @@ inquirer
152152
let content = fs.readFileSync(file, 'utf8');
153153

154154
// Skip flagged files
155-
if (content.match(/\/\/ @remove-file-on-eject/)) {
155+
const pluginMatch = content.match(/\/\/ @remove-file-on-eject (\w+-?)+/);
156+
if (pluginMatch) {
157+
if (!hasPlugin(pluginMatch.pop())) {
158+
return;
159+
}
160+
} else if (content.match(/\/\/ @remove-file-on-eject/)) {
156161
return;
157162
}
158163
// Remove plugins

packages/react-scripts/scripts/utils/createJestConfig.js

+40-10
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
const fs = require('fs');
1313
const chalk = require('chalk');
1414
const paths = require('../../config/paths');
15+
const { hasPlugin } = require('react-dev-utils/plugins');
1516

1617
module.exports = (resolve, rootDir, isEjecting) => {
18+
const hasTypeScript = hasPlugin('typescript');
19+
1720
// Use this instead of `paths.testsSetup` to avoid putting
1821
// an absolute filename into configuration after ejecting.
1922
const setupTestsFile = fs.existsSync(paths.testsSetup)
@@ -23,27 +26,54 @@ module.exports = (resolve, rootDir, isEjecting) => {
2326
// TODO: I don't know if it's safe or not to just use / as path separator
2427
// in Jest configs. We need help from somebody with Windows to determine this.
2528
const config = {
26-
collectCoverageFrom: ['src/**/*.{js,jsx}'],
29+
collectCoverageFrom: [
30+
'src/**/*.{js,jsx}',
31+
...(hasTypeScript ? ['src/**/*.{ts,tsx}'] : []),
32+
],
2733
setupFiles: [resolve('config/polyfills.js')],
2834
setupTestFrameworkScriptFile: setupTestsFile,
2935
testMatch: [
3036
'<rootDir>/src/**/__tests__/**/*.js?(x)',
3137
'<rootDir>/src/**/?(*.)(spec|test).js?(x)',
38+
...(hasTypeScript
39+
? [
40+
'<rootDir>/src/**/__tests__/**/*.ts?(x)',
41+
'<rootDir>/src/**/?(*.)(spec|test).ts?(x)',
42+
]
43+
: []),
3244
],
3345
testEnvironment: 'node',
3446
testURL: 'http://localhost',
35-
transform: {
36-
'^.+\\.(js|jsx)$': isEjecting
37-
? '<rootDir>/node_modules/babel-jest'
38-
: resolve('config/jest/babelTransform.js'),
39-
'^.+\\.css$': resolve('config/jest/cssTransform.js'),
40-
'^(?!.*\\.(js|jsx|css|json)$)': resolve('config/jest/fileTransform.js'),
41-
},
42-
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
47+
transform: Object.assign(
48+
{
49+
'^.+\\.(js|jsx)$': isEjecting
50+
? '<rootDir>/node_modules/babel-jest'
51+
: resolve('config/jest/babelTransform.js'),
52+
'^.+\\.css$': resolve('config/jest/cssTransform.js'),
53+
},
54+
hasTypeScript
55+
? { '^.+\\.(ts|tsx)$': resolve('config/jest/typescriptTransform.js') }
56+
: {},
57+
{
58+
'^(?!.*\\.(js|jsx|css|json)$)': resolve('config/jest/fileTransform.js'),
59+
}
60+
),
61+
transformIgnorePatterns: [
62+
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$',
63+
...(hasTypeScript ? ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$'] : []),
64+
],
4365
moduleNameMapper: {
4466
'^react-native$': 'react-native-web',
4567
},
46-
moduleFileExtensions: ['web.js', 'js', 'json', 'web.jsx', 'jsx', 'node'],
68+
moduleFileExtensions: [
69+
'web.js',
70+
'js',
71+
...(hasTypeScript ? ['tsx', 'ts'] : []),
72+
'json',
73+
'web.jsx',
74+
'jsx',
75+
'node',
76+
],
4777
};
4878
if (rootDir) {
4979
config.rootDir = rootDir;

0 commit comments

Comments
 (0)