使 Typescript 2 路径映射(别名)工作 [英] Getting Typescript 2 paths mapping (aliases) working

查看:22
本文介绍了使 Typescript 2 路径映射(别名)工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用以下配置为我的打字稿应用程序引用自定义模块快捷方式(即使用 ts 路径映射功能).

I am trying to reference custom modules shortcuts (ie use ts paths mapping feature) for my typescript app, with the following config.

项目结构

dist/

src/
  lyrics/
     ... ts files
  app/
     ... ts files

完整的项目结构在这里:github.com/adadgio/npm-lyrics-ts,dist文件夹当然不提交)

Full project structure is here: github.com/adadgio/npm-lyrics-ts, dist folder is not commited of course)

tsconfig.json

{
    "compilerOptions": {
        "outDir": "dist",
        "module": "commonjs",
        "target": "es6",
        "sourceMap": true,
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "removeComments": true,
        "noImplicitAny": false,
        "baseUrl": ".",
        "paths": {
            "*": ["src/lyrics/*"], // to much here !! but none work
            "zutils/*": ["./src/lyrics/*", "src/lyrics/utils/*", "dist/lyrics/utils/*"]
        },
        "rootDir": "."
    },
    "exclude": [
        "dist",
        "node_modules"
    ],
    "include": [
        "src/**/*.ts"
    ]
}

当我运行我的 npm start/compile 或 watch 脚本时,我没有收到 Typescript 错误.以下作品(Atom 是我的 IDE)

When i run my npm start/compile or watch script, i get no Typescript errors. The following works (Atom is my IDE)

// string-utils.ts does exist, no IDE error, typescript DOES compile
`import { StringUtils } from 'zutils/string-utils';` 

但我随后收到以下错误:

But i then get the NodeJS following error:

Error: Cannot find module 'zutils/string-utils'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/src/index.ts:7:1)
    at Module._compile (module.js:571:32)
    at Module.m._compile (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/node_modules/ts-node/src/index.ts:413:23)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/home/adadgio/WebServer/projects/adadgio/npm-lyrics-ts/node_modules/ts-node/src/index.ts:416:12)
    at Module.load (module.js:488:32)

看起来该模块正试图从 node_modules 文件夹中解析.我已经阅读了关于 Typescript 路径映射的文档,但我无法让它工作.

It looks like the module is trying to be resolved from the node_modules folder. I've read docs about Typescript paths mapping but i cannot get it to work.

推荐答案

我对此进行了大量研究.我正在使用 atom、typescript 和 nodejs.

I was doing lot of research on this. I am using atom, typescript and nodejs.

问题是,当您编译 typescript 时,它会搜索路径(要包含的 .ts 文件的路径).但是,最终编译的 .js 文件不会被路径替换.

The thing is, when you compile typescript, it does search for paths (the path to a .ts file to include). However, the final compiled .js file does not get path substituted.

解决办法:

  • 编译ts文件,使用tsconfig中的路径
  • 使用脚本替换最终 .js 文件中的路径标记
  • 运行节点应用程序

所以本质上,tsconfig 的一部分看起来像这样

So essentially, part of tsconfig will look like this

  "baseUrl": "./app",
    "paths" : {
      "@GameInstance" : ["model/game/GameInstance"],
      "@Map" : ["model/game/map/Map"],
      "@MapCreator" : ["model/game/map/creator/MapCreator"],
      "@GameLoop" : ["model/game/GameLoop"],
      "@Point" : ["model/other/math/geometry/Point"],
      "@Rectangle" : ["model/other/math/geometry/Rectangle"],
      "@Functions" : ["model/other/Functions"]

    }

并考虑 Rectangle.ts 文件

And consider Rectangle.ts file

import { Point } from '@Point';
import { Vector } from '@Vector';
/**
 * Represents a rectangle.
 * You can query it for collisions or whether rectangles are touching
 */
export class Rectangle {
//more code

Rectangle.ts 在哪里

Where Rectangle.ts is in

./src/app/model/other/math/geometry/Rectangle/Rectangle.ts

我们跑

tsc

这将编译我们设置的所有 .ts 文件.在那里,路径将在运行时被替换,如果出现错误,请运行

which will compile all .ts files we set up. There, the paths will be substituted in runtime, if you get error, run

tsc --traceResolution > tmp && gedit tmp

并搜索未定义路径包含的字段.您将能够看到替换日志

and search for the fiel wehere is undefined path include. You will be able to see substitution log

然后我们就剩下 Rectangle.js(构建)

Then we are left with Rectangle.js (builded)

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var _Point_1 = require("@Point");
//more code

如您所见,@Point 将不存在,我们无法像这样运行节点应用程序.

As you see, the @Point will not exist and we can not run node application like this.

然而,为此,我创建了一个脚本,它递归地搜索 js 文件并通过转到根目录然后到目标路径来替换令牌.

For that, however, I created a script, that recursivelly searches js files and replaces the tokens by going up to root and then to target path.

有 requirePaths.data 文件,您可以在其中定义令牌和路径.

There is requirePaths.data file where you define token and the path.

'@GameLoop' : "./app/model/game/GameLoop"
'@GameInstance' : "./app/model/game/GameInstance"
"@Map" : "./app/model/game/map/Map"
"@MapCreator" : "./app/model/game/map/creator/MapCreator"
"@Point" : "./app/model/other/math/geometry/Point"
"@Rectangle" : "./app/model/other/math/geometry/Point"

现在,这不是通用的,它只是我的热门脚本.请注意,该结构是

Now, this is NOT universal, it is just my hot script. Please, take a note, that structure is

src
|-app
|  |-model
-build
   |-src
      |-app
          |-model
|-test

从技术上讲,src 中的 app/model... 结构只是复制到 src/buildtsc 从/src/app 获取源代码并编译它.编译结果在/src/build

Technically, the app/model... structure in src, is just copied to the src/build The tsc takes source from /src/app and compiles it. Compiled result is in /src/build

然后,有substitutePathsInJS.sh 脚本.这会扫描构建路径,每当它找到标记@Rectangle,它就会替换它(更多解释如下...)代码:

Then, there is the substitutePathsInJS.sh script. This scand for build path, and whenever it finds token @Rectangle, it replaces it (more explanation below...) Code:

    #!/bin/bash

function testreqLevel()
{
  local srcPath="$1"
  local replacingIn="$2"
  local expectedLevel=$3
  getPathLevel "$replacingIn"
  local res=$?
  if [ ! $res -eq $expectedLevel ]; then
    echo "[-] test $srcPath , $replacingIn FAILED. ($res != $expectedLevel)"
  fi
}
function assertreqPath()
{
  local input="$1"
  local expected="$2"
  if [ ! "$input" = "$expected" ]; then
    echo "[-] test $expected FAILED"
    echo "computed: $input"
    echo "expected: $expected"
  fi
}
function testGetPathLevel()
{
  testreqLevel "./build/src" "./build/src/app/model/game/GameObject.js" 4
  testreqLevel "./build/src" "./build/src/file.js" 1
  testreqLevel "./build/src" "./build/src/app/model/game/GameObject.js" 4
}
function testGetPathToRoot()
{
  local path=$(getPathToRoot "./build/src" "./build/src/app/model/game/GameObject.js")
  assertreqPath "$path" "../../../../../"

  path=$(getPathToRoot "./" "./server.js")
  assertreqPath "$path" "./"

  path=$(getPathToRoot "./" "./app/model/game/GameInstance.js")
  assertreqPath "$path" "../../../"
}
function err()
{
  echo "[-] $1"
}
function getPathLevel()
{
  #get rid of starting ./
  local input=$(echo "$1" | sed "s/^\.\///")

  local contains=$(echo "$input" | grep '/')
  if [ -z "$contains" ]; then
    return 0
  fi
  #echo "$input"
  local slashInput=$(echo "$input" | awk -F/ '{print NF-1}')
  return $(($slashInput - 1))
}
#given ROOT, and PATH, returns a path P such as being in directory PATH, from there using P we get to root
#example:
#ROOT=./src
#PATH=./src/model/game/objects/a.js
#returns ../../
function getPathToRoot()
{
  local root="$1"
  local input="$2"
  getPathLevel "$input"
  local level=$?

  if [ $level -eq 0 ]; then
    echo "./"
    return 0
  fi
  for ((i=1; i <= level + 1; i++)); do
    echo -n '../'
  done
  #echo "$root" | sed 's/^\.\///'
}
function parseData()
{
echo "**************"
echo "**************"
  local data="$1"

  let lineNum=1
  while read -r line; do
    parseLine "$line" $lineNum
    if [ $? -eq 1 ]; then
      return 1
    fi
    let lineNum++
  done <<< "$data"
  echo 'Parsing ok'

  echo "**************"
  echo "**************"
  return 0
}
function parseLine()
{
  if [[ "$1" =~ ^\#.*$ ]] || [[ "$1" =~ ^\ *$ ]]; then
    #comment line
    return 0
  fi

  local line=$(echo "$1" | sed "s/\"/'/g")
  let lineNum=$2

  local QUOTE=\'
  local WORD_IN_QUOTES=$QUOTE[^:$QUOTE]*$QUOTE

  if [[ "$line" =~ ^\ *$WORD_IN_QUOTES\ *:\ *$WORD_IN_QUOTES\ *$ ]]; then
    # valid key : value pair
    local key=$(echo "$line" | awk -F: '{print $1}' | sed 's/^ *//g' \
    | sed 's/ *$//g' | sed 's/\//\\\//g' | sed "s/'//g" | sed "s/\./\\\./g")
    local val=$(echo "$line" | awk -F: '{print $2}' | sed 's/^ *//g' \
    | sed 's/ *$//g' | sed "s/'//g")
    echo "[+] Found substitution from '$key' : '$val'"

    if [ -z "$REPLACEMENT_KEY_VAL" ]; then
      REPLACEMENT_KEY_VAL="$key|$val"
    else
      REPLACEMENT_KEY_VAL="$REPLACEMENT_KEY_VAL;$key|$val"
    fi
  else
    err "Parse error on line $lineNum"

    echo "Expecting lines 'token' : 'value'"
    return 1
  fi
  return 0
}
function replaceInFiles()
{
  cd "$WHERE_SUBSTITUTE"
  echo "substitution root $WHERE_SUBSTITUTE"

  local fileList=`find . -type f -name "*.js" | grep -v "$EXCLUDE"`

  echo "$fileList"| while read fname; do
    export IFS=";"
    echo "Replacing in file '$WHERE_SUBSTITUTE$fname'"
    for line in $REPLACEMENT_KEY_VAL; do
      local key=`echo "$line" | awk -F\| '{print $1}'`
      local val=`echo "$line" | awk -F\| '{print $2}'`

      local finalPath=$(getPathToRoot "./" "$fname")"$val"

      if [ $VERBOSE -eq 1 ]; then
        echo -e "\tsubstitute '$key' => '$val'"
        #echo -e "\t$finalPath"
        echo -e "\treplacing $key -> $finalPath"
      fi

      #escape final path for sed
      #slashes, dots
      finalPath=$(echo "$finalPath" | sed 's/\//\\\//g'| sed 's/\./\\\./g')

      if [ $VERBOSE -eq 1 ]; then
        echo -e '\t\tsed -i.bak '"s/require(\(.\)$key/require(\1$finalPath/g"\ "$fname"
      fi
      sed -i.bak "s/require(\(.\)$key\(.\))/require(\1$finalPath\2)/g" "$fname"
    done
  done
 return 0
}
function quit()
{
  echo "*************************************"
  echo "*****SUBSTITUTING PATHS EXITING******"
  echo "*************************************"
  echo
  exit $1
}
#######################################
CURRENTDIR=`dirname "$(realpath $0)"`
WHERE_SUBSTITUTE='./build/src'
REPLACEMENT_KEY_VAL="";
VERBOSE=0

FILE="$CURRENTDIR/requirePaths.data"
EXCLUDE='./app/view'

if [ "$1" = "-t" ]; then
  testGetPathLevel
  testGetPathToRoot
  echo "[+] tests done"
  exit 0
fi

if [ "$1" = "-v" ]; then
  VERBOSE=1
fi
echo "*************************************"
echo "********SUBSTITUTING PATHS***********"
echo "*************************************"
if [ ! -f "$FILE" ]; then
  err "File $FILE does not exist"
  quit 1
fi

DATA=`cat "$FILE"`
parseData "$DATA"
if [ $? -eq 1 ]; then
  quit 1
fi
replaceInFiles
quit $?

这似乎令人困惑,但请考虑一下示例.我们有 Rectangle.js 文件.

This seems confusing, but consider excample. We have Rectangle.js file.

脚本从 requirePaths.data 文件加载一堆输入标记,在这种情况下,让我们关注行

The script loads bunch of input tokens from requirePaths.data file, in this case, lets focus on line

"@Point" : "./app/model/other/math/geometry/Point"

脚本从 ./src 运行,并被赋予根目录 ./src/build/src

Script runs from ./src, and is given root directory ./src/build/src

脚本执行 cd ./src/build/src

Script does cd ./src/build/src

执行查找.在那里,它将收到

Executes find. There, it will receive

./model/other/math/geometry/Rectangle/Rectangle.ts

绝对路径是

./src/build/src/app/model/other/math/geometry/Rectangle/Rectangle.ts

但我们现在不关心绝对路径.

But we do not care about absolute path now.

计算他从目录上去的路径Whi将结果类似于

Calculates path such as he gets from the directory up Whis will result is something like

./../../../../

他喜欢从目录中获取的位置

Where he will like that get from directory

/src/build/app/model/other/math/geometry/Rectangle

到目录

/src/build/app

然后,在该字符串后面,我们添加从数据文件提供的路径

Then, behind that string, we add the path provided from the data file

./../../../.././app/model/other/math/geometry/Point

所以文件 Rectangle.js(在 BUILD 文件夹中)的最终替换是

So the final substitution for file Rectangle.js (in BUILD folder somewhere) is

之前

require("@Point")

之后

require("./../../../.././app/model/other/math/geometry/Point")

这很糟糕,但无论如何我们都不关心 js 中的内容.最重要的是它有效.

Which is terrible, but we do not care about what is in js anyway. Main thing is that it works.

缺点

  1. 您不能将其与代码监视器结合使用.监控 tsc 然后,当代码更改完成时,进行自动 tsc 编译,然后自动运行 shell 路径替换,然后在最终的 js 文件上 tun nodeJS 是可能的,但由于某种原因,然后 sh 脚本替换路径,监控软件认为是代码的变化(不知道为什么,它已经从监视器中排除了构建)并再次编译.因此,你产生了一个无限循环

  1. You can NOT combine it with code monitor. Monitoring tsc and then, when change in code is done, do automatic tsc compile, then automatically run the shell path substitution and then tun nodeJS upon final js files is possible, BUT for some reason, then the sh script substitutes paths, the monitoring software considers is as change in code (no idea why, it has build excluded from monitor) and compiles again. Therefore, you gen an infinite loop

您必须手动编译它,一步一步,或者只在 tsc 编译时使用监视器.当你编写一些代码时,去运行替换并测试 nodeJS 功能.

You must compile it manually, step by step, or JUST use monitor on tsc compilation. When you write some code, go and run substitution and test the nodeJS functionality.

当添加新的Food类时,你必须为它定义一个标记(@Food)和2个地方的文件路径(tsconfig)和shell脚本的输入

When adding new Class Food, you must define a token for it (@Food) and path to file in 2 places (tsconfig) and the input for shell script

你使整个编译过程变长了.老实说,无论如何,tsc 花费了大部分时间,而 bash 脚本并没有那么耗时令人惊讶....

You make entire compilation process longer. To be honest, tsc takes most of time anyway, and the bash script is not so time consuming surprisingly....

使用 mocha 进行测试时,您必须再次进行逐步编译,完成后,在最终 js 文件上方运行 mocha.但是为此您可以编写脚本....

When implementing tests with mocha, you must again do step by step compilation and when finished, run mocha above final js files. But for that you can write scripts....

有些人通常只替换像@app 或某些目录.问题在于,无论何时移动源文件,都必须进行大量更改...

Some people usually substitute only like @app or some directories. The problem with that is, whenever you move source file around, you have to do lot of changes...

好的一面

  1. 移动文件时,您会更改一个字符串(在两个地方....)
  2. 不再有使大项目无法维护的相对路径
  3. 很有趣,但确实有效,而且我没有遇到大问题(如果使用得当)

这篇关于使 Typescript 2 路径映射(别名)工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆