I'm currently implementing Typescript on my project.
And I've found out something curious.
You can see on the App.tsx file below that I had to import firebase from "firebase/app"
in order to gain access to the firebase.app.App
type.
App.tsx
import React from "react";
import firebase from "firebase/app";
interface App_PROPS {
firebase: firebase.app.App | null;
};
firebase.initializeApp({});
const App: React.FC<App_PROPS> = () => {
return(
<div>App</div>
)
};
export default App;
And I thought that this would become a bug in my project, because since I do SSR, that import CANNOT happen on my server code, because firebase/app
is not meant to be used on the server. That is why the firebase
package is passed passed down props
, by the way.
But for my surprise, the package is not imported at all:
When I transpile it with babel using babel-loader
(via webpack), this is what I get:
App.js - transpiled with babel
var _react = _interopRequireDefault(require("react"));
// (...) SOME OTHER CODE
// I WILL NOT POST THE FULL CODE HERE
// BUT "firebase/app" WAS NOT IMPORTED
Also when I transpile it using tsc src/App.tsx
App.js - transpiled with tsc
"use strict";
exports.__esModule = true;
var react_1 = require("react");
;
var App = function () {
return (<div>App</div>);
};
exports["default"] = App;
// "firebase/app" WAS NOT IMPORTED
Could anybody explain to me what is happening in this situation? I'm glad the transpilers don't import the package, but why is that?
Is it because I'm only using the package inside an interface {}
? So, after transpiling for typescript that interface will be gone (since it cannot exist in JS) and the package will be dropped by some tree-shaking
algorithm?
OBS: I've also tried the same code, but this time calling firebase.initializeApp()
, which is a method from the firebase package. And in this case, the firebase/app
package is imported in the transpiled versions of the App.tsx
file, which makes sense.
It all comes down to Does this import been use as a value? Or just Type?
I'll explain:
Taken this for example:
import firebase from "firebase/app";
If you use firebase.initializeApp()
- you use its value, the generated javascript from typescript compilation need to supply initalizeApp
function and have it run in runtime - after compilation ends. This means it needs to have access to firebase
import thus typescript compilation doesn't cut this import and later webpack transfer it to __webpack__require.
On the other hand, if you use firebase: firebase.app.App | null;
- you use its type.
You are saying to typescript compiler: "While you type safe firebase
property, make sure its of type firebase.app.App
." - After this type safe check, there is no longer need for anything related to firebase.
When typescript compiles a file, it checks if it uses only Types from a given import. If it does, it cuts that import.
SideNote:
In Typescript 3.8 import type
keywords is introduce.
It let you define
import type firebase from "firebase/app";
Which means this import can only be used for types. If you'll try firebase.initializeApp()
, the build will fail.
That will be classic for your scenario in case you are concern from future developers using this import as a value also.