Skip to content

Commit

Permalink
Add support for Typescript development (#652)
Browse files Browse the repository at this point in the history
* WIP: Typescript support

* WIP

* WIP

* Lint fixes

* Add ts file

* Upgrade Prettier and lint

* Lint fix

* Edit tsconfig include/exclude

* Run tsc on lint

* Remove separate type check

* Convert another file to ts

* Remove noEmit

* Fix Jest + TS
  • Loading branch information
kmjennison authored May 28, 2023
1 parent 31f3940 commit 522185a
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 117 deletions.
70 changes: 46 additions & 24 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
module.exports = {
extends: ['airbnb', 'prettier'],
plugins: ['prettier', 'react-hooks'],
extends: [
'airbnb',
'prettier',
'plugin:@typescript-eslint/recommended',
'plugin:import/typescript',
],
parser: '@typescript-eslint/parser',
plugins: ['prettier', 'react-hooks', '@typescript-eslint'],
root: true,
rules: {
'prettier/prettier': 'error',
'react/jsx-filename-extension': 0,
Expand All @@ -26,12 +33,36 @@ module.exports = {
unnamedComponents: 'arrow-function',
},
],
// https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#i-am-using-a-rule-from-eslint-core-and-it-doesnt-work-correctly-with-typescript-code
'no-shadow': 0,
'@typescript-eslint/no-shadow': 'error',
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-defsine.md
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': ['error'],
// Let eslint manage semicolons
'@typescript-eslint/no-extra-semi': 0,
'import/no-unresolved': 'error',
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
},
overrides: [
// Set Jest rules only for test files.
// https://stackoverflow.com/a/49211283
{
files: ['**/*.test.js', '**/__mocks__/**/*.js'],
files: [
'**/*.test.ts',
'**/__mocks__/**/*.ts',
'**/*.test.js',
'**/__mocks__/**/*.js',
],
extends: ['plugin:jest/recommended'],
env: {
jest: true,
Expand All @@ -40,33 +71,21 @@ module.exports = {
rules: {
'global-require': 0,
'react/jsx-props-no-spreading': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-empty-function': 0,
},
},
{
files: ['./codemod/**'],
rules: {
'import/no-extraneous-dependencies': 0,
'@typescript-eslint/no-var-requires': 0,
},
},
// Handle TypeScript separately.
{
files: ['**/*.ts', '**/*.tsx'],
extends: ['plugin:@typescript-eslint/recommended'],
plugins: ['@typescript-eslint'],
parser: '@typescript-eslint/parser',
settings: {
'import/resolver': {
// enable `eslint-import-resolver-typescript`
typescript: {},
},
},
files: ['./codemod/**/*.fixtures/*'],
rules: {
// https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#i-am-using-a-rule-from-eslint-core-and-it-doesnt-work-correctly-with-typescript-code
'no-shadow': 0,
'@typescript-eslint/no-shadow': 'error',
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-defsine.md
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': ['error'],
'@typescript-eslint/no-unused-vars': 0,
},
},
{
Expand All @@ -89,11 +108,14 @@ module.exports = {
},
settings: {
// Handle linting for absolute imports.
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
alias: [
['codemod', './codemod'],
['src', './src'],
],
typescript: {
alwaysTryTypes: true,
},
node: true,
},
},
}
2 changes: 2 additions & 0 deletions example/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react'
import Link from 'next/link'

/* eslint-disable @typescript-eslint/no-var-requires */
const nfaDependencyVersion =
require('../package.json').dependencies['next-firebase-auth']
const nextDependencyVersion = require('../package.json').dependencies.next
const firebaseDependencyVersion =
require('../package.json').dependencies.firebase
const firebaseAdminDependencyVersion =
require('../package.json').dependencies['firebase-admin']
/* eslint-enable @typescript-eslint/no-var-requires */

const styles = {
container: {
Expand Down
2 changes: 1 addition & 1 deletion example/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const config = {}

// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
Expand Down
2 changes: 1 addition & 1 deletion example/pages/api/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ initAuth()
const handler = async (req, res) => {
try {
// Including unused return value to demonstrate codemod
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const { AuthUser } = await setAuthCookies(req, res)
} catch (e) {
// eslint-disable-next-line no-console
Expand Down
8 changes: 7 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
Expand All @@ -9,13 +10,18 @@ module.exports = {
],
coverageDirectory: './coverage/',
setupFilesAfterEnv: ['<rootDir>/jestSetup.js'],
// Support absolute imports; e.g. from 'src/*'
// https://stackoverflow.com/a/72437265/1332513
moduleDirectories: ['node_modules', '<rootDir>'],
testPathIgnorePatterns: ['/node_modules/', '/.next/', '/.yalc/', '/.git/'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(js|jsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(ts|tsx)$': 'ts-jest',
},
transformIgnorePatterns: [
'/node_modules/(?!(firebase|@firebase)/)',
'/.yalc/',
'^.+\\.module\\.(css|sass|scss)$',
],
testEnvironment: 'node',
}
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@
"build": "npm-run-all -s build:clean build:src",
"build:clean": "rm -rf ./build",
"build:src": "NODE_ENV=production webpack",
"lint": "eslint ./",
"lint": "tsc --noEmit && eslint ./",
"bundlesize": "npm-run-all -s build bundlesize:no-build",
"bundlesize:no-build": "bundlesize",
"test": "npm-run-all -s install-example-deps lint test:coverage test:types",
"test": "npm-run-all -s install-example-deps lint test:coverage",
"test:run": "jest --env=jsdom",
"test:coverage": "yarn run test:run --coverage",
"test:watch": "yarn run test:run --watch",
"test:types": "npm-run-all -s test:types:info test:types:run",
"test:types:run": "tsc",
"test:types:info": "echo \"\n\n******\nReminder: build before testing types!\n******\n\"",
"install-example-deps": "cd example && yarn",
"dev:publish": "npm-run-all -s build dev:yalc-publish-push",
"dev:yalc-publish-push": "yalc publish --push",
Expand All @@ -48,6 +45,7 @@
"@testing-library/react": "^13.3.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/cookies": "^0.7.7",
"@types/jest": "^28",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"babel-jest": "^28.1.3",
Expand All @@ -61,7 +59,7 @@
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^3.3.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^26.6.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
Expand All @@ -79,12 +77,15 @@
"next": "^12.2.3",
"next-test-api-route-handler": "^3.1.6",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"prettier": "^2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
"regenerator-runtime": "^0.13.9",
"set-cookie-parser": "^2.5.0",
"source-map-loader": "^4.0.1",
"ts-jest": "^28",
"ts-loader": "^9.4.3",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^4.5.0",
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ const defaultConfig = {
tokenChangedHandler: undefined,
// Optional function: handler called if there are unexpected errors while
// verifying the user's ID token server-side.
// eslint-disable-next-line @typescript-eslint/no-empty-function
onVerifyTokenError: () => {},
// Optional function: handler called if there are unexpected errors while
// refreshing the user's ID token server-side.
// eslint-disable-next-line @typescript-eslint/no-empty-function
onTokenRefreshError: () => {},
// Optional string: the URL to navigate to when the user
// needs to log in.
Expand Down
1 change: 1 addition & 0 deletions src/cookies.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const getCookie = (
res = {
getHeader: () => [],
setHeader: () => ({
// eslint-disable-next-line @typescript-eslint/no-empty-function
call: () => {},
}),
},
Expand Down
5 changes: 3 additions & 2 deletions src/createAuthUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,15 @@ const createAuthUser = ({
let getIdTokenFunc = async () => null

// When not on the client side, the "signOut" method is a noop.
// eslint-disable-next-line @typescript-eslint/no-empty-function
let signOutFunc = async () => {}

let tokenString = null // used for serialization
if (firebaseUserClientSDK) {
if (isClientSide()) {
// eslint-disable-next-line global-require
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { getApp } = require('firebase/app')
// eslint-disable-next-line global-require
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { getAuth, signOut } = require('firebase/auth')

signOutFunc = async () => signOut(getAuth(getApp()))
Expand Down
2 changes: 1 addition & 1 deletion src/index.server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable global-require */
/* eslint-disable global-require, @typescript-eslint/no-var-requires */

import initCommon from 'src/initCommon'
import AuthAction from 'src/AuthAction'
Expand Down
1 change: 0 additions & 1 deletion src/isClientSide.js

This file was deleted.

1 change: 1 addition & 0 deletions src/isClientSide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (): boolean => typeof window !== 'undefined'
2 changes: 1 addition & 1 deletion src/testHelpers/createMockConfig.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-env jest */

const createMockConfig = ({ clientSide } = {}) => {
// eslint-disable-next-line global-require
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const isClientSide = require('src/isClientSide').default
const useClientSideConfig =
typeof clientSide === 'undefined' ? isClientSide() : clientSide
Expand Down
21 changes: 15 additions & 6 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"paths": {
"src/*": [
"./src/*"
],
"codemod/*": [
"./codemod/*"
]
},
"target": "es5",
"lib": [
"dom",
Expand All @@ -10,7 +20,6 @@
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
Expand All @@ -19,12 +28,12 @@
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
"**/*"
],
"exclude": [
"node_modules",
"example"
"build",
"coverage",
"example",
"node_modules"
]
}
12 changes: 12 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const nodeExternals = require('webpack-node-externals')
Expand All @@ -14,13 +15,24 @@ const sharedConfig = {
libraryTarget: 'commonjs2',
libraryExport: 'default',
},
resolve: {
extensions: ['.js', '.jx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: 'ts-loader',
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: 'babel-loader',
},

// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: 'source-map-loader' },
],
},
externals: [
Expand Down
Loading

0 comments on commit 522185a

Please sign in to comment.