Usar caminhos absolutos com TypeScript, Babel e Browserify

Como importar módulos usando caminhos não relativos em TypeScript e como compilar o resultado para JavaScript com a correcta resolução de caminhos.

Quando usamos caminhos relativos num projecto TypeScript de um certo tamanho e complexidade, as importações começam a ficar assim:

import { SpecialCollection } from "../../special";
import { LoginComponent } from "../login";
import { TextUtils } from ".../../utils/text";
import { Router } from "../../../core/router";

Torna-se incómodo importar módulos e fica mais difícil modificar a estrutura ou o código.

A solução apresentada neste artigo resume-se ao seguinte:

  • Configurar o TypeScript para usar caminhos não relativos.
  • Usar TypeScript para compilar o código para ES6.
  • Configurar o Babel para transformar os caminhos.
  • Usar Babel para compilar o código para ES5.
  • Combinar o resultado para o browser usando o Browserify.

Definir caminhos personalizados em TypeScript

O compilador de TypeScript pode ser configurado com as seguintes propriedades:

  • baseUrl: o caminho base para os módulos.
  • paths: mapeamento de caminhos relativos a baseUrl.

Podem ler mais sobre estas e outras opções em Module Resolution.

Vamos considerar a seguinte estrutura de um projecto:

src
│   main.ts
│ 
└───core
│   │   router.ts
│   
└───components
    │   special.ts
    │
    └───auth
    │   │   login.ts
    │
    └───utils
        │   text.ts

Podemos configurar o tsconfig.json assim:

{
    "files": [
        "src/main.ts"
    ],
    "compilerOptions": {
        "target": "es6",
        "baseUrl": "./src",
        "paths": {
            "@core/*": ["core/*"],
            "@components/*": ["components/*"],
            "@auth/*": ["components/auth/*"],
            "@utils/*": ["components/utils/*"]
        }
    }
}

As importações podem ser agora alteradas da seguinte forma:

import { SpecialCollection } from "@components/special";
import { LoginComponent } from "@auth/login";
import { TextUtils } from "@utils/text";
import { Router } from "@core/router";

Definir caminhos personalizados em Babel

Dissemos ao TypeScript para compilar o código para ES6. Vamos usar Babel para transformar os caminhos e compilar para ES5.

Para transformar os caminhos em Babel vamos usar babel-plugin-module-resolver.

O nosso .babelrc poderá ser assim:

{
    "presets": ["env"],
    "plugins": [
        ["module-resolver", {
            "root": ["./src"],
            "alias": {
                "@core": "./src/core",
                "@components": "./src/components",
                "@auth": "./src/components/auth",
                "@utils": "./src/components/utils"
            }
        }]
    ]
}

root e alias são parecidos a baseUrl e paths do TypeScript mas notem as diferenças.

Construir o resultado final

Agora podemos criar um script gulp para fazer a compilação.

Criem o projecto:

npm init

Instalem os pacotes necessários:

npm install --save-dev babel-plugin-module-resolver babel-preset-env babelify browserify gulp tsify typescript vinyl-source-stream watchify

Exemplo de um gulpfile.js:

var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var watchify = require("watchify");
var babelify = require("babelify");

var watchedBrowserify =
    watchify(
        browserify({
            "basedir": ".",
            "entries": "src/main.ts"
            "debug": true,
            "cache": {},
            "packageCache": {}
        })
        .plugin(tsify)
        .transform(babelify, { "extensions": [".js", ".ts"] })
    );

function build() {
    return watchedBrowserify
        .bundle()
        .on('error', function(error){
            console.log(error.message);
            this.emit('end');
        })
        .pipe(source("bundle.js"))
        .pipe(gulp.dest("dist"));
}

gulp.task("build", [], build);
watchedBrowserify.on("update", build);
watchedBrowserify.on('log', function (msg) {
    console.log(msg);
});

tsconfig.json e .babelrc serão lidos automaticamente pelos plugins.

Em resumo, os ficheiros fonte em TypeScript vão ser compilados usando tsify, depois transformados usando babelify onde os caminhos finais serão definidos e finalmente será tudo agrupado para o browser com Browserify.

Nuno Freitas
Publicado por Nuno Freitas em 14 outubro, 2017

Artigos relacionados