-
Notifications
You must be signed in to change notification settings - Fork 16
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
feat: create initial TS template #868
Changes from all commits
7dd58b6
d612cf5
8db46a0
07a7f86
c509324
7d65159
2651c2a
4220ede
8632214
a5f6970
959b482
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,10 @@ | ||
/cli/assets | ||
**/locales/index.js | ||
# These are to avoid lint errors like 'cannot find module App.jsx' | ||
cli/config/init/entrypoint.jsx | ||
cli/config/init/App.test.jsx | ||
cli/config/templates/init/entrypoint.jsx | ||
cli/config/templates/init/App.test.jsx | ||
cli/config/templates/init-typescript/entrypoint.tsx | ||
cli/config/templates/init-typescript/App.test.tsx | ||
cli/config/templates/init-typescript/global.d.ts | ||
cli/config/templates/init-typescript/modules.d.ts | ||
cli/config/templates/init-typescript/eslint.config.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const defaultsApp = { | ||
type: 'app', | ||
|
||
entryPoints: { | ||
app: './src/App.tsx', | ||
}, | ||
} | ||
|
||
const defaultsLib = { | ||
type: 'lib', | ||
|
||
entryPoints: { | ||
lib: './src/index.tsx', | ||
}, | ||
} | ||
|
||
const defaultsPWA = { | ||
pwa: { | ||
/** | ||
* If true, service worker is registered to perform offline caching | ||
* and use of cacheable sections & recording mode is enabled | ||
*/ | ||
enabled: false, | ||
caching: { | ||
/** | ||
* If true, don't cache requests to exteral domains in app shell. | ||
* Doesn't affect recording mode | ||
*/ | ||
omitExternalRequestsFromAppShell: false, | ||
/** Deprecated version of above */ | ||
omitExternalRequests: false, | ||
/** | ||
* Don't cache URLs matching patterns in this array in app shell. | ||
* Doesn't affect recording mode | ||
*/ | ||
patternsToOmitFromAppShell: [], | ||
/** Deprecated version of above */ | ||
patternsToOmit: [], | ||
/** | ||
* Don't cache URLs matching these patterns in recorded sections. | ||
* Can still be cached in app shell unless filtered there too. | ||
*/ | ||
patternsToOmitFromCacheableSections: [], | ||
/** | ||
* In addition to the contents of an app's 'build' folder, other | ||
* URLs can be precached by adding them to this list which will | ||
* add them to the precache manifest at build time. The format of | ||
* this list must match the Workbox precache list format: | ||
* https://developers.google.com/web/tools/workbox/modules/workbox-precaching#explanation_of_the_precache_list | ||
*/ | ||
additionalManifestEntries: [], | ||
/** | ||
* By default, all the contents of the `build` folder are added to | ||
* the precache to give the app the best chances of functioning | ||
* completely while offline. Developers may choose to omit some | ||
* of these files (for example, thousands of font or image files) | ||
* if they cause cache bloat and the app can work fine without | ||
* them precached. See LIBS-482 | ||
* | ||
* The globs should be relative to the public dir of the built app. | ||
* Used in injectPrecacheManifest.js | ||
*/ | ||
globsToOmitFromPrecache: [], | ||
}, | ||
}, | ||
} | ||
|
||
module.exports = { defaultsApp, defaultsLib, defaultsPWA } |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.container { | ||
width: 100%; | ||
height: 100%; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: 1rem; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { CustomDataProvider } from '@dhis2/app-runtime' | ||
import React from 'react' | ||
import ReactDOM from 'react-dom' | ||
import App from './App' | ||
|
||
it('renders without crashing', () => { | ||
const div = document.createElement('div') | ||
|
||
const data = { | ||
resource: 'test', | ||
} | ||
|
||
ReactDOM.render( | ||
<CustomDataProvider data={data}> | ||
<App /> | ||
</CustomDataProvider>, | ||
div | ||
) | ||
ReactDOM.unmountComponentAtNode(div) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const config = { | ||
type: 'app', | ||
|
||
entryPoints: { | ||
app: './src/App.tsx', | ||
}, | ||
} | ||
|
||
module.exports = config |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const config = { | ||
type: 'lib', | ||
|
||
entryPoints: { | ||
lib: './src/index.tsx', | ||
}, | ||
} | ||
|
||
module.exports = config |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useDataQuery } from '@dhis2/app-runtime' | ||
import i18n from '@dhis2/d2-i18n' | ||
import React, { FC } from 'react' | ||
import classes from './App.module.css' | ||
|
||
interface QueryResults { | ||
me: { | ||
name: string | ||
} | ||
} | ||
|
||
const query = { | ||
me: { | ||
resource: 'me', | ||
}, | ||
} | ||
|
||
const MyApp: FC = () => { | ||
const { error, loading, data } = useDataQuery<QueryResults>(query) | ||
|
||
if (error) { | ||
return <span>{i18n.t('ERROR')}</span> | ||
} | ||
|
||
if (loading) { | ||
return <span>{i18n.t('Loading...')}</span> | ||
} | ||
|
||
return ( | ||
<div className={classes.container}> | ||
<h1>{i18n.t('Hello {{name}}', { name: data?.me?.name })}</h1> | ||
<h3>{i18n.t('Welcome to DHIS2 with TypeScript!')}</h3> | ||
</div> | ||
) | ||
} | ||
|
||
export default MyApp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import js from '@eslint/js' | ||
import tseslint from 'typescript-eslint' | ||
|
||
export default [ | ||
js.configs.recommended, | ||
...tseslint.configs.recommended, | ||
{ | ||
ignores: ['*.d.ts', 'd2.config.js'], | ||
}, | ||
{ | ||
files: ['**/*.{ts,tsx}'], | ||
languageOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
}, | ||
}, | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import 'react' | ||
|
||
declare module 'react' { | ||
interface StyleHTMLAttributes<T> extends React.HTMLAttributes<T> { | ||
jsx?: boolean | ||
global?: boolean | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
declare module '*.module.css' { | ||
const classes: { [key: string]: string } | ||
export default classes | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"noEmit": true, | ||
"skipLibCheck": true, | ||
"allowJs": true, | ||
"jsx": "react", | ||
"esModuleInterop": true, | ||
"target": "ESNext", | ||
"module": "esnext", | ||
"moduleResolution": "node" | ||
}, | ||
"include": ["src", "types"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,22 +69,25 @@ const writeGitignore = (gitignoreFile, sections) => { | |
fs.writeFileSync(gitignoreFile, gitignore.stringify(sections, format)) | ||
} | ||
|
||
const handler = async ({ force, name, cwd, lib }) => { | ||
const handler = async ({ force, name, cwd, lib, typeScript }) => { | ||
// create the folder where the template will be generated | ||
cwd = cwd || process.cwd() | ||
cwd = path.join(cwd, name) | ||
fs.mkdirpSync(cwd) | ||
const paths = makePaths(cwd) | ||
const paths = makePaths(cwd, { typeScript }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Random brainstorming that I think is out of scope for this PR:] It would be cool if passing the |
||
|
||
reporter.info('checking d2.config exists') | ||
if (fs.existsSync(paths.config) && !force) { | ||
reporter.warn( | ||
'A config file already exists, use --force to overwrite it' | ||
) | ||
} else { | ||
reporter.info('Importing d2.config.js defaults') | ||
reporter.info('Importing d2.config defaults') | ||
fs.copyFileSync( | ||
lib ? paths.initConfigLib : paths.initConfigApp, | ||
paths.config | ||
) | ||
reporter.debug(' copied default d2.config') | ||
} | ||
|
||
if (!fs.existsSync(paths.package)) { | ||
|
@@ -183,7 +186,74 @@ const handler = async ({ force, name, cwd, lib }) => { | |
}) | ||
} | ||
|
||
const entrypoint = lib ? 'src/index.jsx' : 'src/App.jsx' | ||
if (typeScript) { | ||
// copy tsconfig | ||
reporter.info('Copying tsconfig') | ||
fs.copyFileSync(paths.initTSConfig, paths.tsConfig) | ||
|
||
reporter.info('install TypeScript as a dev dependency') | ||
// ToDO: restrict the major version of TS we install | ||
await exec({ | ||
cmd: 'yarn', | ||
args: ['add', 'typescript', '--dev'], | ||
cwd: paths.base, | ||
}) | ||
|
||
// install any other TS dependencies needed | ||
reporter.info('install type definitions') | ||
await exec({ | ||
cmd: 'yarn', | ||
args: [ | ||
'add', | ||
'@types/react @types/react-dom @types/jest', | ||
'@types/eslint', | ||
'--dev', | ||
], | ||
cwd: paths.base, | ||
}) | ||
|
||
// add global.d.ts to get rid of CSS module errors | ||
// something like https://github.com/dhis2/data-exchange-app/pull/79/files#diff-858566d2d4cf06579a908cb85f587c5752fa0fa6a47d579277749006e86f0834 | ||
// (but maybe something better) | ||
// also look at copying src/custom.d.ts https://github.com/dhis2/data-exchange-app/pull/79/files#diff-5f2ca1b1541dc3023f81543689da349e59b97c708462dd8da4640b399362edc7 | ||
reporter.info('add declaration files') | ||
const typesDir = path.join(paths.base, 'types') | ||
|
||
if (!fs.existsSync(typesDir)) { | ||
fs.mkdirpSync(typesDir) | ||
} | ||
fs.copyFileSync( | ||
paths.initGlobalDeclaration, | ||
path.join(typesDir, 'global.d.ts') | ||
) | ||
fs.copyFileSync( | ||
paths.initModulesDeclaration, | ||
path.join(typesDir, 'modules.d.ts') | ||
) | ||
|
||
// ToDO: make custom eslint config part of the template (and copy it) | ||
// similar to: https://github.com/dhis2/data-exchange-app/pull/79/files#diff-e2954b558f2aa82baff0e30964490d12942e0e251c1aa56c3294de6ec67b7cf5 | ||
// install dependencies needed for eslint | ||
// "@typescript-eslint/eslint-plugin" | ||
// "@typescript-eslint/parser" | ||
|
||
reporter.info('setting up eslint configuration') | ||
await exec({ | ||
cmd: 'yarn', | ||
args: ['add', 'eslint @eslint/js typescript-eslint', '--dev'], | ||
cwd: paths.base, | ||
}) | ||
// copy eslint config | ||
fs.copyFileSync(paths.initEslint, paths.eslintConfig) | ||
|
||
// ToDO: we're hardcoding running TS, we need to figure out how to pass the argument from the CLI | ||
|
||
// ToDO: aim to have a TS project that runs with "yarn start" and "yarn build" | ||
} | ||
|
||
const extension = typeScript ? 'ts' : 'js' | ||
|
||
const entrypoint = lib ? `src/index.${extension}x` : `src/App.${extension}x` | ||
|
||
if (fs.existsSync(path.join(paths.base, entrypoint))) { | ||
reporter.warn( | ||
|
@@ -197,7 +267,7 @@ const handler = async ({ force, name, cwd, lib }) => { | |
if (!lib) { | ||
fs.copyFileSync( | ||
paths.initAppTestJsx, | ||
path.join(paths.base, 'src/App.test.jsx') | ||
path.join(paths.base, `src/App.test.${extension}x`) | ||
) | ||
fs.copyFileSync( | ||
paths.initAppModuleCss, | ||
|
@@ -242,6 +312,12 @@ const command = { | |
type: 'boolean', | ||
default: false, | ||
}, | ||
typeScript: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suuuper subjective and equally minor since the alias is set up, and maybe I’m crazy, but I’ll share in case it’s in the back of your mind too: |
||
alias: ['typescript', 'ts'], | ||
description: 'Use TypeScript template', | ||
type: 'boolean', | ||
default: false, | ||
}, | ||
}, | ||
handler, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this file is needed -- these are more JS code to import as defaults, rather than boilerplate to be copied into a new app
(I actually meant to add a comment about this when separating these objects from the boilerplate files, but forgot to add it in the last PR, so it's in a more recent one: d2ConfigDefaults.js)