diff --git a/development-setup.md b/development-setup.md new file mode 100644 index 00000000000..062b1258714 --- /dev/null +++ b/development-setup.md @@ -0,0 +1,163 @@ +# Development setup + +(for https://github.com/NLeSC/create-react-app) + + +## long story short + +``` +npm install -g create-react-app +npm install -g verdaccio +verdaccio +npm set registry http://localhost:4873 +git clone https://github.com/NLeSC/create-react-app.git +cd create-react-app/packages/react-scripts + +npm version +npm adduser --registry http://localhost:4873/ +npm publish + +cd +create-react-app my-app --scripts-version @nlesc/react-scripts + +``` + + + +## long story long + +A user who just wants to automatically generate an initial setup for a React app +could do so by installing the ``create-react-app`` generator as follows: +``` +npm install -g create-react-app +``` + +Afterwards, you will have a command available that will allow you to run the +line below and create a new React app: +``` +create-react-app +``` + +This will create a directory ``your-app-name`` that contains a complete setup for +developing the newly created React app, including testing, code coverage generation, +bundling, minification, incremental buidling, linting, etc. + +Note that the above uses the ``npm`` registry at ``https://registry.npmjs.org/``. +Now let's say you'd want to make changes to the template used to generate new +apps, for example because you want new apps to be in TypeScript instead of the +default JavaScript. For this, it is useful to run your own, local ``npm`` registry +using [verdaccio](https://github.com/verdaccio/verdaccio). Verdaccio sits between +you and https://registry.npmjs.com. It lets you publish development versions of +your npm package to the local, private repo, while still being able to retrieve +any other packages from the 'normal' registry at npmjs. + +Install verdaccio with: + +``` +npm install -g verdaccio +``` + +Check to see if it works: + +``` +verdaccio +``` + +``verdaccio`` should tell you where its registry lives. Mine is at +``http://localhost:4873/``. We will now tell ``npm`` to use the local +``verdaccio`` registry instead of ``http://registry.npmjs.com``, as follows: +``` +npm set registry http://localhost:4873 +``` +If you want to return to the normal setup at a later point in time, you can do +so with: +``` +npm set registry https://registry.npmjs.com +``` + +Let's say you now want to make some changes to the ``create-react-app`` +generator application. First get the source code of ``create-react-app`` using +nlesc's fork. I'm checking out the source code into ``~/github/nlesc/``: +``` +cd ~/github/nlesc/ +git clone https://github.com/NLeSC/create-react-app.git +``` + +Any changes you make will likely be in one of ``create-react-app``'s constituent +packages, which are located at ``create-react-app/packages``. Each of its +subdirectories is a separate ``npm`` package. Let's say you want to make changes +to the App template from ``create-react-app/packages/react-scripts/template/src``. + +``` +cd create-react-app/packages/react-scripts/template/src + +``` + +Before publishing your changes, you need to: + +1. increment the semantic versioning of the package +1. add yourself as a user to verdaccio's npm registry server + +Let's first check what the current semantic version number is, as follows. Walk +up the directory tree until you find a ``package.json``. You should find one in +``create-react-app/packages/react-scripts/``. Look up the value for ``version``. +Now go back to the terminal and use ``npm version patch`` to increment the patch +part of the semantic version number. The 'patch' part of the +version value in package.json should have been incremented (reload your editor +if necessary). + +Now let's add a user to verdaccio, as follows: +``` +npm adduser --registry http://localhost:4873/ +``` +This will ask you to enter a user name, password, and e-mail address, which will +be stored in verdaccio. It doesn't really matter what you enter on any of the +three questions. + + +Now look up the value for ``name`` in package.json. This will be the name the +package is published under. For me, it's ``@nlesc/react-scripts``. + +Verify that verdaccio is the active npm registry with ``npm get registry``, and +that verdaccio is in fact up and running, then publish the package to verdaccio +with: + +``` +npm publish +``` + +You can point your browser to http://localhost:4873/ to get an overview of +packages that are present in verdaccio's registry. + +Now when we want to test if the new version of ``@nlesc/react-scripts`` does +what we want it to do, we can ``cd`` to some other place, let's say ``~/tmp``: + +``` +cd ~/tmp +``` +``npm install`` ``@nlesc/react-scripts`` locally (still using verdaccio's repo): +``` +npm install @nlesc/react-scripts +``` + +You can now inspect the package at ``~/tmp/node_modules/@nlesc/react-scripts/``. + +The local install (``npm install`` without the ``-g`` flag) makes it a little +easier to remove the ``node_modules`` directory when additional changes have +been made to the package, and those changes have been ``npm publish``'ed to the +verdaccio registry. + + +Now create a new app using the updated generator as follows: +``` +cd ~/tmp # or wherever you want the new app to be +create-react-app the-new-app --scripts-version @nlesc/react-scripts +``` +This uses the globally installed ``create-react-app``, but with the custom +version of ``react-scripts``, namely ``@nlesc/react-scripts`` from verdaccio. + + + + + + diff --git a/feature-matrix.md b/feature-matrix.md new file mode 100644 index 00000000000..1585e4ea0ca --- /dev/null +++ b/feature-matrix.md @@ -0,0 +1,131 @@ + +# legend + +The table records what is currently available and working _in the listed repo_, not what could work with some extra effort. Recording these facts helps to keep the discussions clean. + +- :grey_question: : unknown +- :white_check_mark: : implemented +- :x: : not implemented + + + +# general + +|feature description | [create-react-app](https://github.com/facebookincubator/create-react-app) | [punchcardjs](https://github.com/nlesc-sherlock/punchcardjs) | [molviewer](https://github.com/3D-e-Chem/molviewer-tsx) | [angular-cli](https://github.com/angular/angular-cli) | [create-ts-app](https://github.com/vgmr/create-ts-app) | yeoman generator | issues | +|---|---|---|---|---|---|---|---| +| transpile from TS to JS | :x: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| transpile TSX to JS | :x: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| transpile errors are terminal | :grey_question: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#5](https://github.com/NLeSC/create-react-app/issues/5) | +| doesn't use ``gulp`` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| doesn't use ``grunt`` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| doesn't use ``bower`` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| doesn't use ``jspm`` | :white_check_mark: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| targets es5 | :white_check_mark: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| targets latest chrome | :grey_question: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| targets latest firefox | :grey_question: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| targets latest edge | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| automatic reload browser on code change | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| automatic rebuild on code changes, manual browser reload | :white_check_mark: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| fast rebuilds | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| can handle TS2 ``@types/`` | :x: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#2](https://github.com/NLeSC/create-react-app/issues/2) | +| in-browser debugging of original source code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#6](https://github.com/NLeSC/create-react-app/issues/6) | +| minification of js | :white_check_mark: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#6](https://github.com/NLeSC/create-react-app/issues/6) | +| minification of css | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| bundling of js | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| bundling of css | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| linting can run on Travis or similar | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#7](https://github.com/NLeSC/create-react-app/issues/7) | +| testing can run on Travis or similar | :white_check_mark: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| building can run on Travis or similar | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#8](https://github.com/NLeSC/create-react-app/issues/8) | +| has ``purge`` command: | :x: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#9](https://github.com/NLeSC/create-react-app/issues/9) | +| has deploy app to gh-pages command | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| has deploy docs to gh-pages command | :x: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#10](https://github.com/NLeSC/create-react-app/issues/10) | +| has generate api docs command | :x: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#11](https://github.com/NLeSC/create-react-app/issues/11) | +| can handle es7 object spread | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#12](https://github.com/NLeSC/create-react-app/issues/12) | +| can handle es7 decorators | :x: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#12](https://github.com/NLeSC/create-react-app/issues/12) | +| can handle es7 generator | :grey_question: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#12](https://github.com/NLeSC/create-react-app/issues/12) | +| can use untyped JS libs | :x: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#13](https://github.com/NLeSC/create-react-app/issues/13) | +| can differentiate prod/dev | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#14](https://github.com/NLeSC/create-react-app/issues/14) | +| can access external api server | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | + + +# testing + +|feature description | [create-react-app](https://github.com/facebookincubator/create-react-app) | [punchcardjs](https://github.com/nlesc-sherlock/punchcardjs) | [molviewer](https://github.com/3D-e-Chem/molviewer-tsx) | [angular-cli](https://github.com/angular/angular-cli) | [create-ts-app](https://github.com/vgmr/create-ts-app) | yeoman generator | issues | +|---|---|---|---|---|---|---|---| +| unit testing | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#15](https://github.com/NLeSC/create-react-app/issues/15) | +| dom testing | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#15](https://github.com/NLeSC/create-react-app/issues/15) | +| e2e testing | :x: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#16](https://github.com/NLeSC/create-react-app/issues/16) | +| testing across browsers/OS/devices | :x: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#16](https://github.com/NLeSC/create-react-app/issues/16) | +| coverage of original code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#17](https://github.com/NLeSC/create-react-app/issues/17) | +| coverage includes untouched code | :grey_question: | :grey_question: | :x: | :grey_question: | :grey_question: | :grey_question: | [#18](https://github.com/NLeSC/create-react-app/issues/18) | +| error stacktrace of original code | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | [#17](https://github.com/NLeSC/create-react-app/issues/17) | +| tests written in TS | :x: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#15](https://github.com/NLeSC/create-react-app/issues/15) | +| has ``it()`` and ``describe()`` or similar | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| has command to run tests | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| automatic run tests on source change | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| Advanced assertions, e.g. Chai | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| Mocking ability, e.g. Sinon | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | + + +# linting + +|feature description | [create-react-app](https://github.com/facebookincubator/create-react-app) | [punchcardjs](https://github.com/nlesc-sherlock/punchcardjs) | [molviewer](https://github.com/3D-e-Chem/molviewer-tsx) | [angular-cli](https://github.com/angular/angular-cli) | [create-ts-app](https://github.com/vgmr/create-ts-app) | yeoman generator | issues | +|---|---|---|---|---|---|---|---| +| tslint by editor plugin | :x: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#19](https://github.com/NLeSC/create-react-app/issues/19) | +| csslint by editor plugin | :x: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#19](https://github.com/NLeSC/create-react-app/issues/19) | +| esjshint by editor plugin | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#19](https://github.com/NLeSC/create-react-app/issues/19) | +| prebuild linting | :grey_question: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#20](https://github.com/NLeSC/create-react-app/issues/20) | +| precommit linting | :grey_question: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| prerelease linting | :grey_question: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| tslint the TS src | :x: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#20](https://github.com/NLeSC/create-react-app/issues/20) | +| eslint src | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| no conflicts between linters | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :white_check_mark: | +| lint css | :x: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#20](https://github.com/NLeSC/create-react-app/issues/20) | +| linter tsx aware | :x: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | [#20](https://github.com/NLeSC/create-react-app/issues/20) | +| has .editorconfig | :x: | :white_check_mark: | :x: | :grey_question: | :grey_question: | :grey_question: | [#21](https://github.com/NLeSC/create-react-app/issues/21) | +| lint errors are terminal | :grey_question: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | [#20](https://github.com/NLeSC/create-react-app/issues/20) | + + + + + +# Remaining + +* Not too many commands +* Not too many config files +* Minimize duplication, eg. installing library should not take many steps +* Opening repo in editor should just work +* Doucment how to install dependencies +* Document usual suspects for routing/state/async/fetch: react-router, react-redux, redux-thunk, isomorphic-fetch +* dependency management: npm, unpkg +* autoprefixing vendor specific css ``-webkit-box-orient`` and such +* static asset management, including fonts, images and such +* avoid browser caching of static assets + + +| allows offline mode | :white_check_mark: | :x: | :x: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | +| allows standalone mode | :grey_question: | :x: | :white_check_mark: | :grey_question: | :grey_question: | :grey_question: | :grey_question: | + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-scripts/README.md b/packages/react-scripts/README.md index 845e546c67c..a2671bf55ab 100644 --- a/packages/react-scripts/README.md +++ b/packages/react-scripts/README.md @@ -1,5 +1,13 @@ # react-scripts +Fork which adds Typescript support to apps created with [Create React App](https://github.com/facebookincubator/create-react-app). + +The `@nlesc/react-scripts` package can be used by: + +``` +create-react-app my-app --scripts-version @nlesc/react-scripts +``` + This package includes scripts and configuration used by [Create React App](https://github.com/facebookincubator/create-react-app). Please refer to its documentation: diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js index 58381833957..31b19689b5e 100755 --- a/packages/react-scripts/bin/react-scripts.js +++ b/packages/react-scripts/bin/react-scripts.js @@ -7,6 +7,7 @@ switch (script) { case 'build': case 'eject': case 'start': +case 'lint': case 'test': var result = spawn.sync( 'node', diff --git a/packages/react-scripts/config/jest/transform.js b/packages/react-scripts/config/jest/transform.js index 11a0149f97d..8f7e381c208 100644 --- a/packages/react-scripts/config/jest/transform.js +++ b/packages/react-scripts/config/jest/transform.js @@ -6,8 +6,66 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +// This file is a merger between the original transform.js and ts-jest/dist/preprocessor.js (https://github.com/kulshekhar/ts-jest/blob/e1f95e524ed62091736f70abf63530f1f107ec03/src/preprocessor.ts) +// The preprocessor from ts-jest could not be used directly, +// because it did not use babel and +// could not get configuration from the right place (../utils/createJestConfig.js) +// instead it was retrieved from of argv which was incomplete + const babelJest = require('babel-jest'); +const tsc = require('typescript'); +const glob = require('glob-all'); +const nodepath = require('path'); +const tsJestUtils = require('ts-jest/dist/utils'); +const getPackageRoot = require('jest-util').getPackageRoot; +const root = getPackageRoot(); -module.exports = babelJest.createTransformer({ +const babelTransformer = babelJest.createTransformer({ presets: [require.resolve('babel-preset-react-app')] }); + +function initializeCache(config) { + const collectCoverage = config.collectCoverage; + const coverageDirectory = config.coverageDirectory; + const coverageReporters = config.coverageReporters; + const collectCoverageFrom = config.collectCoverageFrom; + const testResultsProcessor = config.testResultsProcessor; + global.__ts_coverage__cache__ = {}; + global.__ts_coverage__cache__.sourceCache = {}; + global.__ts_coverage__cache__.coverageConfig = { collectCoverage: collectCoverage, coverageDirectory: coverageDirectory, coverageReporters: coverageReporters }; + global.__ts_coverage__cache__.coverageCollectFiles = + collectCoverage && + testResultsProcessor && + collectCoverageFrom && + collectCoverageFrom.length ? + glob.sync(collectCoverageFrom).map(function (x) { return nodepath.resolve(root, x); }) : []; +} + +function tsProcess(src, path, config) { + if (path.endsWith('.ts') || path.endsWith('.tsx')) { + if (config.testResultsProcessor && !global.__ts_coverage__cache__) { + // initialize only once + initializeCache(config); + } + var transpiled = tsc.transpileModule(src, { + compilerOptions: tsJestUtils.getTSConfig(config.globals, config.collectCoverage), + fileName: path + }); + if (global.__ts_coverage__cache__) { + if (!config.testRegex || !path.match(config.testRegex)) { + global.__ts_coverage__cache__.sourceCache[path] = transpiled.outputText; + } + } + var modified = "require('ts-jest').install();" + transpiled.outputText; + return modified; + } + return src; +} + +// transpile the source with TypeScript, if needed, and then with Babel +module.exports = { + process(src, path, config) { + src = tsProcess(src, path, config); + return babelTransformer.process(src, path); + }, +}; diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 1c154c36164..cfeb2c9062f 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -40,7 +40,7 @@ module.exports = { appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: resolveApp('src/index.tsx'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), testsSetup: resolveApp('src/setupTests.js'), @@ -59,7 +59,7 @@ module.exports = { appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: resolveApp('src/index.tsx'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), testsSetup: resolveApp('src/setupTests.js'), @@ -76,7 +76,7 @@ if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1) appBuild: resolveOwn('../../../build'), appPublic: resolveOwn('../template/public'), appHtml: resolveOwn('../template/public/index.html'), - appIndexJs: resolveOwn('../template/src/index.js'), + appIndexJs: resolveOwn('../template/src/index.tsx'), appPackageJson: resolveOwn('../package.json'), appSrc: resolveOwn('../template/src'), testsSetup: resolveOwn('../template/src/setupTests.js'), diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index c2b544cca54..d83efb87081 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -84,7 +84,7 @@ module.exports = { // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: // https://github.com/facebookincubator/create-react-app/issues/290 - extensions: ['.js', '.json', '.jsx', ''], + extensions: ['.js', '.json', '.jsx', '.ts', '.tsx', ''], alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ @@ -101,8 +101,13 @@ module.exports = { // @remove-on-eject-end module: { // First, run the linter. - // It's important to do this before Babel processes the JS. + // It's important to do this before Babel or TypeScript processes the JS/TS. preLoaders: [ + { + test: /\.(ts|tsx)$/, + loader: 'tslint', + include: paths.appSrc + }, { test: /\.(js|jsx)$/, loader: 'eslint', @@ -110,6 +115,24 @@ module.exports = { } ], loaders: [ + // Process TS with TypeScript and then with Babel + { + test: /\.(ts|tsx)$/, + include: paths.appSrc, + loader: 'awesome-typescript', + query: { + // when TypeScript emits a file, pass it to Babel to provide backwards compatibility + useBabel: true, + // uses the cache to improve dev performance + useCache: true, + babelOptions: { + // @remove-on-eject-begin + babelrc: false, + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + } + } + }, // Process JS with Babel. { test: /\.(js|jsx)$/, diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index e0141b851b4..b1bdd96c4ce 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -90,7 +90,7 @@ module.exports = { // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: // https://github.com/facebookincubator/create-react-app/issues/290 - extensions: ['.js', '.json', '.jsx', ''], + extensions: ['.js', '.json', '.jsx', '.ts', '.tsx', ''], alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ @@ -107,8 +107,13 @@ module.exports = { // @remove-on-eject-end module: { // First, run the linter. - // It's important to do this before Babel processes the JS. + // It's important to do this before Babel or TypeScript processes the JS/TS. preLoaders: [ + { + test: /\.(ts|tsx)$/, + loader: 'tslint', + include: paths.appSrc + }, { test: /\.(js|jsx)$/, loader: 'eslint', @@ -116,6 +121,21 @@ module.exports = { } ], loaders: [ + // Process TS with TypeScript and then with Babel + { + test: /\.(ts|tsx)?$/, + include: paths.appSrc, + loader: 'awesome-typescript', + query: { + // when TypeScript emits a file, pass it to Babel to provide backwards compatibility + useBabel: true, + babelOptions: { + // @remove-on-eject-begin + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + } + } + }, // Process JS with Babel. { test: /\.(js|jsx)$/, diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index b4cad963979..7c84d945ba2 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,14 +1,14 @@ { - "name": "react-scripts", - "version": "0.7.0", - "description": "Configuration and scripts for Create React App.", - "repository": "facebookincubator/create-react-app", + "name": "@nlesc/react-scripts", + "version": "0.0.15", + "description": "Configuration and scripts for Create React App with Typescript.", + "repository": "NLeSC/create-react-app", "license": "BSD-3-Clause", "engines": { "node": ">=4" }, "bugs": { - "url": "https://github.com/facebookincubator/create-react-app/issues" + "url": "https://github.com/NLeSC/create-react-app/issues" }, "files": [ ".babelrc", @@ -23,7 +23,10 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { + "@nlesc/tslint-config-react-app": "^1.0.1", + "@types/jest": "^15.1.32", "autoprefixer": "6.5.1", + "awesome-typescript-loader": "^2.2.4", "babel-core": "6.17.0", "babel-eslint": "7.0.0", "babel-jest": "16.0.0", @@ -61,6 +64,10 @@ "rimraf": "2.5.4", "strip-ansi": "3.0.1", "style-loader": "0.13.1", + "tslint": "^3.15.1", + "tslint-loader": "^2.1.5", + "ts-jest": "^0.1.13", + "typescript": "^2.0.7", "url-loader": "0.5.7", "webpack": "1.13.2", "webpack-dev-server": "1.16.2", diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index fa42f6dcee6..41185ae7022 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -27,7 +27,8 @@ module.exports = function(appPath, appName, verbose, originalDirectory) { 'start': 'react-scripts start', 'build': 'react-scripts build', 'test': 'react-scripts test --env=jsdom', - 'eject': 'react-scripts eject' + 'eject': 'react-scripts eject', + 'lint': 'react-scripts lint', }; fs.writeFileSync( @@ -72,7 +73,25 @@ module.exports = function(appPath, appName, verbose, originalDirectory) { var proc = spawn('npm', args, {stdio: 'inherit'}); proc.on('close', function (code) { if (code !== 0) { - console.error('`npm ' + args.join(' ') + '` failed'); + console.error('`npm ' + args.join(' ') + '` failed'); + return; + } + + // Run another npm install for react and react-dom typescript type definitions + console.log('Installing @types/react and @types/react-dom from npm...'); + console.log(); + // TODO: having to do two npm installs is bad, can we avoid it? + var targs = [ + 'install', + '@types/react', + '@types/react-dom', + '--save-dev', + verbose && '--verbose' + ].filter(function(e) { return e; }); + var proc = spawn('npm', targs, {stdio: 'inherit'}); + proc.on('close', function (code) { + if (code !== 0) { + console.error('`npm ' + targs.join(' ') + '` failed'); return; } @@ -115,4 +134,5 @@ module.exports = function(appPath, appName, verbose, originalDirectory) { console.log(); console.log('Happy hacking!'); }); + }); }; diff --git a/packages/react-scripts/scripts/lint.js b/packages/react-scripts/scripts/lint.js new file mode 100644 index 00000000000..0a2d708dd41 --- /dev/null +++ b/packages/react-scripts/scripts/lint.js @@ -0,0 +1,18 @@ +var spawn = require('cross-spawn'); + +var args = [ + '--config', + 'tslint.json', + '--exclude', + 'src/**/*.d.ts', + 'src/**/*.ts', + 'src/**/*.tsx' +]; +var proc = spawn('tslint', args, { + stdio: 'inherit' +}); +proc.on('exit', (code) => { + process.exit(code); +}); + +// TODO also run eslint diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index 3203c5fcbb9..fe08db70f04 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -1,4 +1,4 @@ -This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). +This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app) using scripts package [@nlesc/react-scripts](https://github.com/NLeSC/create-react-app). Below you will find some information on how to perform common tasks.
You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md). @@ -187,6 +187,25 @@ The generated project includes React and ReactDOM as dependencies. It also inclu npm install --save ``` +### External Typescript typings + +When dependency does not include a Typescript typings try to install it from http://microsoft.github.io/TypeSearch/ +Typings can be installed with `npm`: + +``` +npm install --save-dev @types/ +``` + +If library does not have typings, it possible to add the typings manually to `src/typings.d.ts` file: +``` +// in src/typings.d.ts +declare module 'typeless-package'; + +// in src/app/app.component.ts +import * as typelessPackage from 'typeless-package'; +typelessPackage.method(); +``` + ## Importing a Component This project setup supports ES6 modules thanks to Babel.
diff --git a/packages/react-scripts/template/gitignore b/packages/react-scripts/template/gitignore index 6c96c5cff12..2e16bab8441 100644 --- a/packages/react-scripts/template/gitignore +++ b/packages/react-scripts/template/gitignore @@ -13,3 +13,9 @@ build .DS_Store .env npm-debug.log + +# awesome typescript loader cache +.awcache + +# Editor +.vscode diff --git a/packages/react-scripts/template/src/App.test.js b/packages/react-scripts/template/src/App.test.tsx similarity index 67% rename from packages/react-scripts/template/src/App.test.js rename to packages/react-scripts/template/src/App.test.tsx index b84af98d720..1513e54691f 100644 --- a/packages/react-scripts/template/src/App.test.js +++ b/packages/react-scripts/template/src/App.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + import App from './App'; it('renders without crashing', () => { diff --git a/packages/react-scripts/template/src/App.js b/packages/react-scripts/template/src/App.tsx similarity index 66% rename from packages/react-scripts/template/src/App.js rename to packages/react-scripts/template/src/App.tsx index d7d52a7f38a..f5710d71ed4 100644 --- a/packages/react-scripts/template/src/App.js +++ b/packages/react-scripts/template/src/App.tsx @@ -1,8 +1,9 @@ -import React, { Component } from 'react'; -import logo from './logo.svg'; +import * as React from 'react'; + import './App.css'; +import logo from './logo.svg'; -class App extends Component { +export class App extends React.Component<{}, {}> { render() { return (
@@ -11,11 +12,9 @@ class App extends Component {

Welcome to React

- To get started, edit src/App.js and save to reload. + To get started, edit src/App.tsx and save to reload.

); } } - -export default App; diff --git a/packages/react-scripts/template/src/index.js b/packages/react-scripts/template/src/index.js deleted file mode 100644 index 54c5ef1a427..00000000000 --- a/packages/react-scripts/template/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; -import './index.css'; - -ReactDOM.render( - , - document.getElementById('root') -); diff --git a/packages/react-scripts/template/src/index.tsx b/packages/react-scripts/template/src/index.tsx new file mode 100644 index 00000000000..bcb37f87608 --- /dev/null +++ b/packages/react-scripts/template/src/index.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { App } from './App'; +import './index.css'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/packages/react-scripts/template/src/typings.d.ts b/packages/react-scripts/template/src/typings.d.ts new file mode 100644 index 00000000000..15d09c92849 --- /dev/null +++ b/packages/react-scripts/template/src/typings.d.ts @@ -0,0 +1,5 @@ +/* import all svg files as strings */ +declare module '*.svg' { + const __path__: string; + export default __path__; +} diff --git a/packages/react-scripts/template/tsconfig.json b/packages/react-scripts/template/tsconfig.json new file mode 100644 index 00000000000..6b578647e64 --- /dev/null +++ b/packages/react-scripts/template/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "jsx": "preserve", + "module": "es6", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "strictNullChecks": true, + "target": "es6" + }, + "files": [ + "src/typings.d.ts", + "src/index.tsx" + ] +} diff --git a/packages/react-scripts/template/tslint.json b/packages/react-scripts/template/tslint.json new file mode 100644 index 00000000000..0672ad04fd7 --- /dev/null +++ b/packages/react-scripts/template/tslint.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "@nlesc/tslint-config-react-app" + ] +} diff --git a/packages/react-scripts/utils/createJestConfig.js b/packages/react-scripts/utils/createJestConfig.js index df0238f2587..8bbb721c8e4 100644 --- a/packages/react-scripts/utils/createJestConfig.js +++ b/packages/react-scripts/utils/createJestConfig.js @@ -11,15 +11,23 @@ const pathExists = require('path-exists'); const paths = require('../config/paths'); +const path = require('path'); module.exports = (resolve, rootDir, isEjecting) => { // Use this instead of `paths.testsSetup` to avoid putting // an absolute filename into configuration after ejecting. const setupTestsFile = pathExists.sync(paths.testsSetup) ? '/src/setupTests.js' : undefined; + const compilerOptions = require(path.resolve('./tsconfig.json')).compilerOptions; + // Jest gives `SyntaxError: Unexpected token import` error when ES6 module are emitted + compilerOptions.module = "commonjs"; + // Expected Babel transformer to convert jsx to js + // but Jest gives `SyntaxError: Unexpected token <` error when set to preserve + compilerOptions.jsx = "react"; + const config = { - collectCoverageFrom: ['src/**/*.{js,jsx}'], - moduleFileExtensions: ['jsx', 'js', 'json'], + collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'], + moduleFileExtensions: ['jsx', 'js', 'json', 'ts', 'tsx'], moduleNameMapper: { '^.+\\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': resolve('config/jest/FileStub.js'), '^.+\\.css$': resolve('config/jest/CSSStub.js') @@ -28,14 +36,15 @@ module.exports = (resolve, rootDir, isEjecting) => { setupTestFrameworkScriptFile: setupTestsFile, testPathIgnorePatterns: ['/(build|docs|node_modules)/'], testEnvironment: 'node', + testRegex: "(/__tests__/.*|\.(test|spec))\.(ts|tsx|js|jsx)$", + testResultsProcessor: require.resolve("ts-jest/coverageprocessor"), + scriptPreprocessor: resolve('config/jest/transform.js'), + globals: { + "__TS_CONFIG__": compilerOptions + } }; if (rootDir) { config.rootDir = rootDir; } - if (!isEjecting) { - // This is unnecessary after ejecting because Jest - // will just use .babelrc in the project folder. - config.scriptPreprocessor = resolve('config/jest/transform.js'); - } return config; }; diff --git a/packages/tslint-config-react-app/README.md b/packages/tslint-config-react-app/README.md new file mode 100644 index 00000000000..98b1240c9a3 --- /dev/null +++ b/packages/tslint-config-react-app/README.md @@ -0,0 +1,36 @@ +TSlint configuration used in `@nlesc/react-scripts` package. + +TSlint ruleset which combines: + +* tslint-microsoft-contrib +* tslint-react +* tslint:latest + +To use in `create-react-app` we overridden several rules to make it compatible with webpack and have similar rules to eslint-config-react-app package. + +# Overridden rules + +* import-name, webpack allows for import of assets like svg and css +* jsx-alignment, want linting on jsx +* jsx-no-lambda, want linting on jsx +* jsx-no-string-ref, want linting on jsx +* jsx-no-multiline-js, want linting on jsx +* jsx-self-close, want linting on jsx +* member-access, everything that has no access modifier is public implicitly, so no need to specify public again +* missing-jsdoc, not useful for an application, it would be for a library +* no-any, not everything has to be to typed to allow for incrementally introduction of types for eg. 3rd party libraries +* no-relative-imports, not practical for an application, it would be for a library because it has a package name +* no-for-in-array, tslint in create-react-app does not do typecheck +* no-reserved-keywords, redux requires `type` keyword for a action, the rule doesn't allow us to whitelist `type` so we disabled whole rule +* no-unused-variable:[true, "react"], allow React to be marked used by jsx tags +* restrict-plus-operands, tslint in create-react-app does not do typecheck +* quotemark + + * single, single quote because its less typing + * jsx-double, jsx-double because thats the html way + * avoid-escape, part of tslint:latest + +* variable-name + + * removed check-format, both camelCased and UPPER_CASED vars in same file + * removed allow-pascal-case, PascalCase is reserved for classes diff --git a/packages/tslint-config-react-app/package.json b/packages/tslint-config-react-app/package.json new file mode 100644 index 00000000000..d2618b37100 --- /dev/null +++ b/packages/tslint-config-react-app/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nlesc/tslint-config-react-app", + "version": "1.0.1", + "description": "TSLint configuration used by Create React App", + "main": "tslint.json", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": "NLeSC/create-react-app", + "bugs": { + "url": "https://github.com/NLeSC/create-react-app/issues" + }, + "license": "Apache-2.0", + "dependencies": { + "tslint-microsoft-contrib": "^2.0.13", + "tslint-react": "^1.1.0" + } +} diff --git a/packages/tslint-config-react-app/tslint.json b/packages/tslint-config-react-app/tslint.json new file mode 100644 index 00000000000..11be5024604 --- /dev/null +++ b/packages/tslint-config-react-app/tslint.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "tslint-microsoft-contrib", + "tslint-react", + "tslint:latest" + ], + "rules": { + "import-name": false, + "jsx-alignment": true, + "jsx-no-lambda": true, + "jsx-no-string-ref": true, + "jsx-no-multiline-js": true, + "jsx-self-close": true, + "member-access": false, + "missing-jsdoc": false, + "no-any": false, + "no-relative-imports": false, + "no-for-in-array": false, + "no-reserved-keywords": false, + "no-unused-variable": [true, "react"], + "restrict-plus-operands": false, + "quotemark": [ + true, + "single", + "jsx-double", + "avoid-escape" + ], + "variable-name": [ + true, + "ban-keywords" + ] + } +}