【问题标题】:Getting Typescript 2 paths mapping (aliases) working让 Typescript 2 路径映射(别名)工作
【发布时间】:2017-07-05 17:34:38
【问题描述】:

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

项目结构

dist/

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

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

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)

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

然后我得到以下错误:

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 路径映射的文档,但我无法让它工作。

【问题讨论】:

标签: node.js typescript typescript2.0


【解决方案1】:

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

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

解决办法:

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

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

  "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 文件

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 在哪里

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

我们跑

tsc

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

tsc --traceResolution > tmp && gedit tmp

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

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

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

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

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

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

'@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"

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

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

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

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

    #!/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 文件。

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

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

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

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

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

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

那个的绝对路径是

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

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

计算路径,例如他从目录中获取的路径 Whis 的结果类似于

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

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

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

到目录

/src/build/app

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

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

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

之前

require("@Point")

之后

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

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

缺点

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

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

  3. 添加新的食品类时,您必须为其定义一个标记 (@Food) 和 2 个位置的文件路径 (tsconfig) 以及 shell 脚本的输入

  4. 您使整个编译过程更长。说实话,tsc 反正占用了大部分时间,而 bash 脚本也没有那么费时出人意料....

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

有些人通常只替换@app 或某些目录。这样做的问题是,每当您移动源文件时,您都必须进行大量更改...

好的一面

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

【讨论】:

  • 谢谢。我厌倦了寻找解决方案,所以你让我相信我应该编写自己的脚本,而不是花时间上网。但是我会为此目的使用打字稿。
  • 很高兴为您提供帮助。我想补充一点,使用 C++(为了提高性能)可能会更好,因为我的游戏服务器现在在重建时会像这样编译一个小时。我还以某种方式改进了替换脚本,计算每个源文件的 md5 哈希,仅替换更改的文件。如果您需要任何帮助,(或者我可以将整个脚本发送给您),请与我联系。只是警告:像这样编写自己的脚本非常困难,而且耗时且令人沮丧......
  • 我已经写好了:) 它比你的简单得多,因为我使用的是节点,例如计算相对路径只需使用path.relative(from, to)
  • 您介意在这里分享一段脚本的摘录吗?如果解决方案更简单,算法更快(我猜是这样),这将对我有很大帮助
  • 这里我为你创建了一个 git github.com/vtulin/ts-paths-compile,cmets 在代码中,很简单,如果你需要进一步的帮助,请在那里询问。在 Windows 中测试,但我认为它应该适用于任何平台。
猜你喜欢
  • 2017-05-21
  • 2019-07-16
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
  • 2015-12-12
  • 2019-11-25
  • 2016-11-08
相关资源
最近更新 更多