npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

express-typescript-compile

v0.6.1

Published

Express.js on the fly typescript compilation middleware

Downloads

13

Readme

express-typescript-compile

Express.js on the fly typescript compilation middleware. Full documentation.

Introduction

The main purpose of this package is to give ability for super simple, cheep and fast typescript code running, both, on the server and client side, without need of special configuration of project, like configuration for the bundler, compiler, without need of tons of dependencies.

This package delivers express.js middleware which on the fly, during the request, transpile typescript source file to the javascript code. Such transpiled source is cached and retranspiled only after the changes. As transpilation is not cheep, such approach should be only use within the development workflow, or for prototyping or demoing. On side, you can have the production configuration, with the compiler, bundler, linter ect.

Features

  • typescript/js code on the fly transpilation
  • minimal configuration
  • simple memory caching
  • tsconfig paths support
  • dynamic imports import('abc') support
  • support common.js modules imports by aliasing or/and transformers
  • support json modules imports
  • support css imports
  • no impact on the code, and development workflow
  • rich configuration

Installation

npm install express-typescript-compile

Usage

You can find bunch examples here.

Example minimal setup

This is an example of minimal runnable example of express-typescript-compile usage. In this example we are running the server typescript code trough ts-node-dev, and client code is on the fly compiled by the express-typescript-compile.

├── src
│   ├── index-client.ts    // client side entrypoint
│   └── index-server.ts    // server entry point
├── index.html             // client entry html page
├── package.json           // project package.json
└── tsconfig.json          // tsconfig
// src/index-client.ts
console.log('Hello word');
export {}
// index.html
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<!-- Import of typescript module -->
<script type="module" src="/src/index-client.ts"></script>
</body>
</html>
// src/index-server.ts
import express from 'express';
import { typescriptCompileMiddleware } from 'express-typescript-compile';
const app = express();
// dev - on the fly compilation
app.use(typescriptCompileMiddleware());
// index.html
app.get('/', (req, res) => res.sendFile(process.cwd() + '/index.html'));
// server start
app.listen(3000);
// tsconfig.json
{
    "compilerOptions": {
        // Required just because of 'express' import 
        "esModuleInterop": true,
        // As we are transpiling file by file, we have to use isolatedModules option
        "isolatedModules": true
    }
}
// package.json
{
   "scripts": {
      // runing server code by the ts-node-dev (or ts-node)
      "serve": "ts-node-dev -- src/index-server.ts"
   },
   "dependencies": {
      "express": "^4.17.1",
      "tslib": "^2.0.3"
   },
   "devDependencies": {
      "@types/express": "^4.17.9",
      "typescript": "^4.1.3",
      "express-typescript-compile": "0.1.0",
      "ts-node-dev": "^1.1.1"
   }
}

For more information please go to Api Reference.

Live reload

In order to use live page reload technique please use easy-livereload package like:

import liveReload from 'easy-livereload';
...
app.use(liveReload({
   watchDirs: ['public', 'src/app'].map(i => join(process.cwd(), i)),
   checkFunc: () => true,
}));

Import non typescript/es6 modules

This package by self is resolving only the imports to your source typescript modules, and to installed packages which are exports the es6 modules, as such modules are fully supported by the modern browsers. You still are able to import the commonjs, json, css, ... but you will need to handle that by self, please look at the fallowing list of recipes for more details:

Common.js modules

If your code is tries to import the common.js modules which are coming from the package which is not offers the es modules you have options.

Use import-maps

The modern browsers supports the imports-maps.

// src/index-server.ts
...
// dev - on the fly compilation
app.use(typescriptCompileMiddleware({
   resolve: {
       // marking libs as externals
       externals: ['react', 'react-dom' ]
   }
}));
...
// src/index-client.tsx
import { createElement } from 'react';
import { render } from 'react-dom';
const root = document.createElement('div');
document.body.append(root)
render(<div>Hello word</div>, root);
<!DOCTYPE html>
<html lang="en">
<head>
   <!-- Import maps -->
   <script type="importmap">
        {
            "imports": {
                "react": "https://cdn.skypack.dev/react", // can be also /node_modules/react/...
                "react-dom": "https://cdn.skypack.dev/react-dom"
            }
        }
    </script>
</head>
<body>
    <!-- Just import the tsx module -->
    <script type="module" src="/src/index-client.tsx"></script>
</body>
</html>

Aliasing

typescriptCompileMiddleware provides the way for configuring the aliases. Such aliases can map imports to the local files, but also to the public url, eg. to cdn. As we have skypack.dev we can use it to import es version of the common.js packages

// src/index-server.ts
...
// dev - on the fly compilation
app.use(typescriptCompileMiddleware({
   resolve: {
      alias: {
          // we are mapping all import ... from 'react' to import ... from 'https://cdn.skypack.dev/react' 
         'react': 'https://cdn.skypack.dev/react',
         // we are mapping all import ... from 'react-dom' to import ... from 'https://cdn.skypack.dev/react-dom' 
         'react-dom': 'https://cdn.skypack.dev/react-dom'
      }
   }
}));
...
// src/index-client.tsx
import { createElement } from 'react';
import { render } from 'react-dom';
const root = document.createElement('div');
document.body.append(root)
render(<div>Hello word</div>, root);
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
    <!-- Just import the tsx module -->
    <script type="module" src="/src/index-client.tsx"></script>
</body>
</html>

Transform common.js to es modules

typescriptCompileMiddleware provides the way for configuring custom transformers. There is package called cjstoesm which is a transformer of common.js to es modules, and it is compatible with typescriptCompileMiddleware. Unfortunately cjstoesm has some limitation, please read documentation for better info.

// src/index-server.ts
import { cjsToEsmTransformerFactory } from "cjstoesm";

// dev - on the fly compilation
app.use(typescriptCompileMiddleware({
   compile: {
      // adding the common.js to es modules transform
      transformers: [ cjsToEsmTransformerFactory() ]
   }
}));

Mixing both techniques

Transforming solution will not always solve the problem, some libraries (eg React) can't be handled properly by the cjstoesm as the code contains the conditional imports/exports. If we can't or do not want to use skypack.dev cdn, we can try to mix transformers with aliases, which will firstly map imports to file which are easy to handle by the cjstoesm, and then use cjstoesm transform for converts that files from common.js in to es modules.

// src/index-server.ts
import { cjsToEsmTransformerFactory } from "cjstoesm";

// dev - on the fly compilation
app.use(typescriptCompileMiddleware({
   resolve: {
      alias: {
         // forcing import of production bundles as cjstoesm is not 
         // able to handle development versions  
         'react': 'react/cjs/react.production.min.js',
         'react-dom': 'react-dom/cjs/react-dom.production.min.js',
         'scheduler': 'scheduler/cjs/scheduler.production.min.js'
      }
   },
   compile: {
      // adding the common.js to es modules transform
      transformers: [ cjsToEsmTransformerFactory() ]
   }
}));

Json

To import json modules directly to your typescript module you have to

  1. Enable resolveJsonModule option in your tsconfig.json
  2. Deliver the express middleware which will transform the json files in to the es6 modules on the fly, you can use package called json-es6-loader:
     import jsonEs from 'json-es6-loader';
     // serving json as es6 modules
     app.get('*.json', (req, res, next) => {
         // checking is a module import request
         if (req.moduleImport) {
             // set proper content type
             res.contentType('application/javascript');
             // serve transformed json
             res.send(
                 // transform json to es6 module
                 jsonEs(
                     // simple reading the file by the provided request path
                     readFileSync(join(cwd, req.path)).toString('utf-8')));
             return;
         }
         next();
     });

Css

To import css files directly from your typescript source code you will have to

  1. To preserve compilation error we will create the typescript global declaration like global.d.ts:
    declare module "*.css";
  2. As the express-typescript-compile middleware will leave imports to the *.css within es6 modules, we will need to serve es6 modules which are represents *.css files. So we will add an express middleware:
    // so for all requests for the css files
    app.get('*.css', (req, res, next) => {
        // if the request is the es6 module import
        if (req.moduleImport) {
            // we will send proper content type
            res.contentType('application/javascript');
            // and simple js code which will create the link element
            // to the same path as was requested
            // but that time the link will be to plain css, not to es6 module
            res.send(`
                const link = document.createElement('link');
                link.setAttribute('href', '${req.path}');
                link.setAttribute('rel', 'stylesheet');
                document.body.append(link);
            `);
            return;
        }
        next();
    });
  3. Last think which we have to do is to serve regular *.css file, so we can add simple static files middleware:
     app.use(express.static('.'));

How it works

  1. the middleware on the *.ts, *.tsx files requests (eg. http://localhost:3000/src/index.ts) is
  2. looking in cache and working dir for the file by the request.path (and __mi_ctx query parameter) (eg. ./src/index.ts)
  3. transpile file to es module (respecting tsconfig options) by the typescript package
  4. resolving all imports from the file by the aliases/typescript/enhanced-resolver
  5. replacing all imports by resolved value and mark them by special (__mi) query parameter (eg. import { app } from '/src/app/index.ts?__mi=true,
    import { crateElement } from '/node_modules/react/cjs/react.production.min.js?__mi=true)
  6. if import is resolved to upper dir the additional (__mi_ctx) query param is added
    (eg. import { crateElement } from '/node_modules/react/cjs/react.production.min.js?__mi=true&__mi_ctx=../../)
  7. caching file
  8. on the next request to marked resource > go to 2.