firebase / firebase-js-sdk

Firebase Javascript SDK
https://firebase.google.com/docs/web/setup
Other
4.83k stars 889 forks source link

Cannot load firestore with typescript #1008

Closed CheezeCoder closed 6 years ago

CheezeCoder commented 6 years ago

[REQUIRED] Describe your environment

[REQUIRED] Describe the problem

I am trying to create a small module that exports my auth and firestore components. This is a typescript react mobx project. My package.json file looks like this:

{
  "name": "typescript-react-mobx-boilerplate",
  "version": "1.0.0",
  "private": true,
  "description": "A frontend boilerplate with React, MobX and Typescript",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --mode development --hot --progress --colors --port 3000 --host 0.0.0.0 --open",
    "build": "webpack -p --progress --colors",
    "prettier": "prettier --write \"src/**/*.{ts,tsx,css}\""
  },
  "license": "MIT",
  "devDependencies": {
    "@types/classnames": "^2.2.3",
    "@types/googlemaps": "^3.30.9",
    "@types/markerclustererplus": "^2.1.33",
    "@types/node": "^9.4.6",
    "@types/react": "^16.0.40",
    "@types/react-dom": "^16.0.4",
    "@types/react-router": "^4.0.22",
    "@types/react-router-dom": "^4.2.6",
    "@types/webpack": "^3.8.8",
    "automapper-ts": "^1.9.0",
    "awesome-typescript-loader": "^5.2.0",
    "babel-loader": "^7.1.3",
    "css-loader": "^0.28.10",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "file-loader": "^1.1.11",
    "html-loader": "^1.0.0-alpha.0",
    "html-webpack-plugin": "^3.2.0",
    "mobx-react-devtools": "^4.2.15",
    "postcss": "^6.0.19",
    "postcss-browser-reporter": "^0.5.0",
    "postcss-cssnext": "^3.1.0",
    "postcss-import": "^11.1.0",
    "postcss-loader": "^2.1.1",
    "postcss-reporter": "^5.0.0",
    "postcss-url": "^7.3.1",
    "prettier": "^1.11.1",
    "react-hot-loader": "^4.0.0",
    "style-loader": "^0.20.2",
    "typescript": "^2.9.2",
    "url-loader": "^1.0.0-beta.0",
    "webpack": "^4.12.1",
    "webpack-cleanup-plugin": "^0.5.1",
    "webpack-cli": "^2.0.10",
    "webpack-dev-server": "^3.1.4",
    "webpack-hot-middleware": "^2.21.1"
  },
  "dependencies": {
    "classnames": "^2.2.5",
    "firebase": "^5.1.0",
    "luxbar": "^0.3.2",
    "mini-css-extract-plugin": "^0.4.1",
    "mobx": "^3.6.1",
    "mobx-react": "^4.4.2",
    "mobx-react-router": "^4.0.1",
    "npm": "^6.1.0",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-google-maps": "^9.4.5",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "react-toastify": "^4.1.0",
    "react-transition-group": "^1.2.1"
  }
}

and my webpack.config file looks like this:

var webpack = require('webpack');
var path = require('path');

// variables
var isProduction = process.argv.indexOf('-p') >= 0;
var sourcePath = path.join(__dirname, './src');
var outPath = path.join(__dirname, './dist');

// plugins
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');

module.exports = {
  context: sourcePath,
  entry: {
    main: './main.tsx'
  },
  output: {
    path: outPath,
    filename: 'bundle.js',
    chunkFilename: '[chunkhash].js',
    publicPath: isProduction ? '/Wedding' : '/'
  },
  target: 'web',
  resolve: {
    extensions: ['.js', '.ts', '.tsx'],
    // Fix webpack's default behavior to not load packages with jsnext:main module
    // (jsnext:main directs not usually distributable es6 format, but es6 sources)
    mainFields: ['module', 'browser', 'main'],
    alias: {
      app: path.resolve(__dirname, 'src/app/'),
      assets: path.resolve(__dirname, 'src/assets/')
    }
  },
  module: {
    rules: [
      // .ts, .tsx
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: [
          isProduction
            ? 'awesome-typescript-loader'
            : {
                loader: 'babel-loader',
                options: {
                  babelrc: false,
                  plugins: ['react-hot-loader/babel']
                }
              },
          isProduction
            ? null
            : {
                loader: 'awesome-typescript-loader',
                options: {}
              }
        ]
      },

      // css
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              query: {
                modules: true,
                sourceMap: !isProduction,
                importLoaders: 1,
                localIdentName: '[local]__[hash:base64:5]'
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                ident: 'postcss',
                plugins: [
                  require('postcss-import')({ addDependencyTo: webpack }),
                  require('postcss-url')(),
                  require('postcss-cssnext')(),
                  require('postcss-reporter')(),
                  require('postcss-browser-reporter')({
                    disabled: isProduction
                  })
                ]
              }
            }
          ]
        })
      },
      {
        test: /\.css$/,
        include: /node_modules/,
        use: ['style-loader', 'css-loader']
      },
      // static assets
      { test: /\.html$/, use: 'html-loader' },
      { test: /\.(png|jpg|gif)$/, use: 'url-loader?limit=10000' },
      { test: /\.webm$/, use: 'file-loader' }
    ]
  },
  optimization: {
    splitChunks: {
      name: true,
      cacheGroups: {
        commons: {
          chunks: 'initial',
          minChunks: 2
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          priority: -10
        }
      }
    },
    runtimeChunk: true
  },
  plugins: [
    new WebpackCleanupPlugin(),
    new ExtractTextPlugin({
      filename: 'styles.css',
      disable: !isProduction
    }),
    new HtmlWebpackPlugin({
      template: 'assets/index.html'
    })
  ],
  devServer: {
    contentBase: sourcePath,
    hot: true,
    inline: true,
    historyApiFallback: {
      disableDotRule: true
    },
    stats: 'minimal'
  },
  devtool: 'cheap-module-eval-source-map',
  node: {
    // workaround for webpack-dev-server issue
    // https://github.com/webpack/webpack-dev-server/issues/60#issuecomment-103411179
    fs: 'empty',
    net: 'empty'
  }
};

Steps to reproduce:

With this setup my firebase module looks like this:

import * as firebase from 'firebase/app';
import '@firebase/firestore';
import '@firebase/auth';

const config = {
  apiKey: '###########################',
  authDomain: '#############firebaseapp.com',
  databaseURL: 'https://#################.firebaseio.com',
  projectId: '###############'
};

export const app = = firebase.initializeApp(config);
export const auth = firebase.auth(app);
export const database = firebase.firestore(app);

This causes warnings in my loader:

WARNING in ./app/stores/FirebaseStore.tsx 17:18-31
"export 'auth' (imported as 'firebase') was not found in 'firebase/app'

WARNING in ./app/stores/FirebaseStore.tsx 18:22-40
"export 'firestore' (imported as 'firebase') was not found in 'firebase/app'

WARNING in ./app/stores/FirebaseStore.tsx 16:11-33
"export 'initializeApp' (imported as 'firebase') was not found in 'firebase/app'

And then in the console in chrome:

Uncaught TypeError: firebase_app__WEBPACK_IMPORTED_MODULE_0__.initializeApp is not a function
    at Module.eval (FirebaseStore.tsx?c599:17)
    at eval (FirebaseStore.tsx:44)
    at Module../app/stores/FirebaseStore.tsx (24fc1a265799f413611d.js:2048)
    at __webpack_require__ (bundle.js:780)
    at fn (bundle.js:148)
    at Module.eval (index.tsx:7)
    at eval (index.tsx:209)
    at Module../app/pages/Guest/Login/index.tsx (24fc1a265799f413611d.js:1265)
    at __webpack_require__ (bundle.js:780)
    at fn (bundle.js:148)

Its driving me nuts. Ive tried so many combinations to try and fix this and I just can't resolve it. What is going on? I can't seem to get typescript and firestore to play nice together. Please help!

Relevant Code:

Here is the basics of it on stackblitz.

https://stackblitz.com/edit/firebase-issue-sandbox-gtkshz

I assume its got something to do with types not being exported for firestore. I am trying to use this site for our wedding and Im so late with getting things up. Any help as soon as possible would be so helpful! I can provide any missing information otherwise. Thanks so much for anyone giving this their time.

mikelehen commented 6 years ago

I'm not 100% sure, but I don't think the @firebase packages are meant for public usage. Perhaps just changing import '@firebase/...' to import 'firebase/...' will fix the issue?

CheezeCoder commented 6 years ago

Yeah I figured that might be the case.. but it doesn't seem to have any effect at all. I still get the same warnings :(

mikelehen commented 6 years ago

Not 100% sure if it'll solve your issue, but I got your stackblitz working (https://stackblitz.com/edit/firebase-issue-sandbox-hhbeye). Basically I:

CheezeCoder commented 6 years ago

Awesome. Thanks. I’ll check it out when I get back. Am away for the weekend.

CheezeCoder commented 6 years ago

Back now. @mikelehen Ive updated my firebase from 5.1 to 5.2 (didn't realize i was behind :/). Ive created a new stackblitz as I'm using typescript so import firebase from 'firebase' in this case won't work. Im still stuck as even updating my project to 5.2 and using the firebase packages sans '@' still results in the same problems :/ here the new stackblitz with typescript:

https://stackblitz.com/edit/react-ts-dvv5fy?file=index.tsx

Any ideas? What strikes me the most weird is the

WARNING in ./app/stores/FirebaseStore.ts 16:10-32
"export 'initializeApp' (imported as 'firebase') was not found in 'firebase/app'

That I get..,.. its like its not working to import them :/ I don't know why.... maybe something to do with my typescript config and not firebase??

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "jsx": "react",
    "module": "es6",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": false,
    "noImplicitAny": false,
    "noImplicitReturns": false,
    "keyofStringsOnly": true,
    "noUnusedLocals": true,
    "removeComments": true,
    "strictNullChecks": false,
    "outDir": "build",
    "lib": ["es6", "es7", "dom"],
    "baseUrl": "src",
    "paths": {
      "app/*": ["./app/*"]
    }
  },
  "exclude": ["dist", "build", "node_modules"]
}
mmermerkaya commented 6 years ago

Looks like you missed this step:

It should still work like that. What error does it give when you try this?

Edit: Also, the recommended way is to do this:

import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
CheezeCoder commented 6 years ago

@mmermerkaya That won't compile with typescript.

mmermerkaya commented 6 years ago

Can you provide an error message?

CheezeCoder commented 6 years ago

@mmermerkaya

image

✖ 「atl」: Checking finished with 1 errors                                                                                                                        ✖ 「wdm」:    690 modules

ERROR in [at-loader] ./src/app/stores/FirebaseStore.ts:1:8
    TS1192: Module '"C:/Development/WeddingClient/node_modules/firebase/index"' has no default export.
ℹ 「wdm」: Failed to compile.
mmermerkaya commented 6 years ago

You should add "esModuleInterop": true to your tsconfig.json. TypeScript team "highly recommends applying it both to new and existing projects". See TS 2.7 release notes for more information (search for "esModuleInterop", it's close to the bottom of the page).

If it still doesn't work, try changing "module": "es6" to "module": "commonjs" in your TSConfig.

CheezeCoder commented 6 years ago

@mmermerkaya Interesting. Changing the mode to commonjs seems to work! Hallelujah! This with having my imports as import * as firebase from 'firebase' still though and NOT including esModuleInterop. However if I leave my mode at es6 with mode at es6 and DO include esModuleInterop and try import to a standard import import firebase from 'firebase' I get an error

✖ 「atl」: Checking finished with 1 errors                                                                                                                        ✖ 「wdm」:    690 modules

ERROR in [at-loader] ./src/app/stores/FirebaseStore.ts:1:8
    TS1192: Module '"C:/Development/WeddingClient/node_modules/firebase/index"' has no default export.
ℹ 「wdm」: Failed to compile.

So it seems the module dictates the problem here as it seems to be irrelevant of whether esModuleInterop is true or not.

Now... if I I leave module at es6 and DO include both esModuleInterop and allowSyntheticDefaultImports as true it COMPILES!

To summarize:

{ "compilerOptions": { "module": "commonjs" }} => COMPILES { "compilerOptions": { "module": "es6", "esModuleInterop": true, }} => FAILS { "compilerOptions": { "module": "es6", "esModuleInterop": true, "allowSyntheticDefaultImports": true, }} => COMPILES { "compilerOptions": { "module": "commonjs", "esModuleInterop": true, }} => COMPILES { "compilerOptions": { "module": "commonjs", "esModuleInterop": true, "allowSyntheticDefaultImports": true, }} => COMPILES

What's really frustrating is that regardless of what my .tsconfig file is set at VS Code gives me an error in the editor even though it compiles :

image

:(

I wish I understood all of this a bit better.

mmermerkaya commented 6 years ago

{ "compilerOptions": { "module": "es6", "esModuleInterop": true, }} => FAILS { "compilerOptions": { "module": "es6", "esModuleInterop": true, "allowSyntheticDefaultImports": true, }} => COMPILES

That is really weird, because unless you're explicitly disabling it, enabling esModuleInterop should also enable allowSyntheticDefaultImports by default. :confused: See its definition here.

AFAIK, changing module to "commonjs" delegates all module resolution to Webpack, and Webpack knows how to find and import submodules better than TypeScript.

VSCode usually needs a restart when you change TSConfig settings. I created a tiny project to test it and it seems to work on my machine.

Also you should import firebase from 'firebase/app', not from 'firebase' :slightly_smiling_face:

CheezeCoder commented 6 years ago

@mmermerkaya Yeah, a lot of this stuff is over my head. However seeing as how things are playing nicely with the current typescript syntax import * as A from 'A', i think ill just go with the "mode": "commonjs" route for the moment. Ill have to spend some time reading up on the compilerOptions a bit more when I have time. Thank you so much for your help.

Also you should import firebase from 'firebase/app', not from 'firebase' 🙂

Definitely :)

mmermerkaya commented 6 years ago

With all these tools and compilers and bundlers, it's hard to figure out what does what. And TypeScript definitely has too many compiler options. :smile:

No problem, glad I could help. :slightly_smiling_face:

matt-erhart commented 6 years ago

{ "compilerOptions": { "module": "es6", "esModuleInterop": true, "allowSyntheticDefaultImports": true, }} And a vscode reset fixed my problem.

Before that I couldn't get auth and firestore to load onto firebase. . I could get firebase.auth and firebase.initializeApp with import as firebase from 'firebase/app' but I couldn't get firebase.firestore() no matter what I imported from '@firebase' or 'firebase', which btw is pretty confusing.

Really need some better docs on how to import in different situations, a typescript section in the firebase docs would help a ton of people.