Search code examples
typescriptrequirejscommonjstscwindow-object

TypeScript: How to replace imports with window objects?


I created a TypeScript module which requires a third-party library:

import Dexie from "dexie";

namespace storage {
  ...
}

When I compile my TypeScript file, I get the following JavaScript output:

"use strict";
var dexie_1 = require("dexie");
var storage;
(function (storage) {
  ...
})(storage || (storage = {}));

I am okay with this when using the output in a Node.js environment. But for the usage in a browser I want to replace the var dexie_1 = require("dexie"); with an object from window like: var dexie_1 = window.Dexie;.

Can I replace the require statement in my compiled JS with an object from window (global namespace)? Is there a Gulp plugin or sth. similar around?

My tsconfig.json is this:

{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es5"
  },
  "exclude": [
    "node_modules",
    "typings/browser",
    "typings/browser.d.ts"
  ]
}

Solution

  • Webpack can map require("dexie"); to window.Dexie.

    All you have to do is to declare the following in your webpack.config.js:

    module.exports = {
      externals: {
        'dexie': 'Dexie'
      }
    };
    

    Here is a Minimal Working Example for the sake of completeness:

    Directory Layout:

    • bower_components (directory from bower install)
    • dist (directory from gulp default)
    • node_modules (directory from npm install)
    • src (directory for TypeScript source code)
    • typings (directory from typings install)
    • bower.json (Frontend dependencies)
    • gulpfile.js (Build configuration)
    • index.html (demo page to test webpacked code)
    • index.js (primary entry point for distribution)
    • package.json (workflow and build dependencies)
    • tsconfig.json (TypeScript compiler configuration)
    • webpack.config.json (Webpack configuration)

    src/storage.ts

    /// <reference path="../typings/index.d.ts" />
    import Dexie from "dexie";
    
    namespace storage {
      export function setupDatabase():void {
        let db = new Dexie('MyDatabase');
    
        db.version(1).stores({
          friends: 'name, age'
        });
    
        db.open().then(function () {
          console.log('Initialized database: ' + db.name);
        });
      }
    }
    
    module.exports = storage;
    

    bower.json

    {
      "name": "storage",
      "main": "dist/webpacked.js",
      "private": true,
      "dependencies": {
        "dexie": "^1.4.1"
      }
    }
    

    gulpfile.js

    var gulp = require('gulp');
    var rename = require('gulp-rename');
    var runSequence = require('run-sequence');
    var ts = require('gulp-typescript');
    var tsProject = ts.createProject('tsconfig.json');
    var webpack = require('webpack-stream');
    
    gulp.task('build', function () {
      return gulp.src('src/**/*.ts')
        .pipe(ts(tsProject))
        .pipe(gulp.dest('dist'));
    });
    
    gulp.task('webpack', function () {
      return gulp.src('dist/index.js')
        .pipe(webpack(require('./webpack.config.js')))
        .pipe(rename('webpacked.js'))
        .pipe(gulp.dest('dist'));
    });
    
    gulp.task('default', function (done) {
      runSequence('build', 'webpack', done);
    });
    

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>Demo</title>
        <script src="bower_components/dexie/dist/dexie.js"></script>
        <script src="dist/webpacked.js"></script>
      </head>
      <body>
        <script>
          document.addEventListener("DOMContentLoaded", function() {
            storage.setupDatabase();
          }, false);
        </script>
      </body>
    </html>
    

    index.js

    window.storage = require('./dist/storage');
    

    package.json

    {
      "name": "storage",
      "private": true,
      "devDependencies": {
        "dexie": "^1.4.1",
        "gulp": "^3.9.1",
        "gulp-rename": "^1.2.2",
        "gulp-typescript": "^2.13.6",
        "run-sequence": "^1.2.2",
        "webpack-stream": "^3.2.0"
      }
    }
    

    tsconfig.json

    {
      "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "target": "es5"
      },
      "exclude": [
        "node_modules",
        "typings/browser",
        "typings/browser.d.ts"
      ]
    }
    

    typings.json

    {
      "globalDependencies": {
        "node": "registry:dt/node#6.0.0+20160709114037"
      }
    }
    

    Note: The node entry comes from typings install dt~node --global --save and is needed for TypeScript to resolve module in the module.exports statement.

    webpack.config.js

    module.exports = {
      externals: {
        'dexie': 'Dexie'
      }
    };
    

    Approach:

    The TypeScript code imports Dexie and declares itself using the namespace storage. To follow the commonjs way of dependency management (which is declared in tsconfig.json), the TypeScript code needs to export the storage namespace as a module with: module.exports = storage.

    Because TypeScript doesn't know the module object, we need to get it's definition. The module definition is part of the type definition for node, which we get from the DefinitelyTyped repository with the typings tool using typings install dt~node --global --save. To link node's retrieved type definition in TypeScript, we need to declare /// <reference path="../typings/index.d.ts" />.

    After we compile our TypeScript code (using gulp build), we need to declare an entry point to make our code accessible. This is done in index.js. A successful build outputs our dist/storage.js file, which is referenced in index.js.

    When having the build in place, we can webpack our code (to bundle it for a HTML5 browser). Our webpack.config.js maps the "require" name of our dependency (dexie) to an object from the global namespace (window.Dexie). This guarantees us, that Dexie is not part of our compiled code (dist/webpacked.js).

    Once we have the webpacked.js, we can use it in a browser. But we have to make sure that all external dependencies are referenced in our HTML page (that's why Dexie is also declared as a frontend dependency using Bower).