Skip to content
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

Import statement hoisting causing code to run out of order #1395

Closed
aabounegm opened this issue Jun 26, 2021 · 3 comments
Closed

Import statement hoisting causing code to run out of order #1395

aabounegm opened this issue Jun 26, 2021 · 3 comments

Comments

@aabounegm
Copy link

I am writing some Cloud Functions in TypeScript and using ESBuild as a bundler. Here is the relevant part of my code:
index.ts:

import * as firebase from 'firebase-admin';

firebase.initializeApp();

export { default as myWebhook } from '~/webhooks/myFunction';
// other function exports...

webhooks/myFunction.ts:

import * as functions from 'firebase-functions';
import { addDocument } from '~/helpers/firestore';

export default functions.https.onRequest(async (request, response) => {
  // some irrelevant code that uses `addDocument`
});

helpers/firestore.ts:

import { firestore } from 'firebase-admin';

const db = firestore();

export function addDocument(doc) {
  return db.collection('myCollection').add(order);
}
// other functions that use `db`...

(code heavily simplified for the issue)

In case you are not familiar with Firebase, the firebase.initializeApp() function must be called before any of the Firebase components are used (in this case, firestore() in helpers/firestore.ts).
The problem is that the generated code has firebase.initializeApp() as the last line of code, even though it needs to be before the import of the re-exported module.
I thought this might be related to #399, but I am not using code splitting or multiple entry points, and this is about the order of statements before imports/exports, not the order of imports themselves.

Note that I worked around the issue by replacing the export { .. as .. } from '..'; syntax with

// side effects first
import myWebhook = require('~/webhooks/myFunction');
export = {
  myWebhook: myWebhook.default,
};

which is uglier and repetitive.

Is this intended behavior or is it a bug? If it is intended behavior, is my workaround the actual fix or is it unintended behavior?

esbuild.config.js
const esbuild = require('esbuild');
const pkg = require('./package.json');

esbuild.build({
  entryPoints: ['src/index.ts'],
  outfile: 'lib/index.js',
  bundle: true,
  sourcemap: true,
  watch: process.argv.includes('--watch'),
  external: Object.keys(pkg.dependencies),
  platform: 'node',
  target: 'node14',
  logLevel: 'info',
});
@evanw
Copy link
Owner

evanw commented Jun 26, 2021

This is just how import and export in JavaScript works. An import or export statement is only a declaration that the file either has dependencies or exposes live bindings to dependents. They just declare properties about the file and are not evaluated inline. At a high level, it works something like this (ignoring complexities such as top-level await):

  1. Order all module files to be evaluated such that dependencies come before dependents
  2. Use import and export to replace references to imports with references to the underlying exports
  3. Ignore all import and export statements with paths since they have now served their purpose
  4. Evaluate all code in the order in step 1

If you have some code that needs to be evaluated after one import but before another, you can move it to a separate file to achieve this. For example, you cannot do this because of the way import works:

// This is wrong, console.log happens last
import './a.js'
console.log('after a, before b')
import './b.js'

But you can do this:

import './a.js'
import './c.js'
import './b.js'

where the file c.js looks like this:

console.log('after a, before b')

If you don't believe me about import evaluation order, you should try it out for yourself in a real JavaScript environment such as in node or in the browser.

@aabounegm
Copy link
Author

Your explanation makes a lot of sense. I am not sure why I didn't think about separating it to another module, but it works.
Thanks a lot for your help.

P.S.: will this be affected by #399 or does it only break when using code splitting?

@evanw
Copy link
Owner

evanw commented Jun 27, 2021

P.S.: will this be affected by #399 or does it only break when using code splitting?

Yes, unfortunately I think this could be affected by #399. But that problem only happens when using code splitting, so it shouldn't happen when code splitting is off (the default).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants