Typescript模块系统在使用momentJS时表现异常

31

我正在尝试在TypeScript中使用momentJs: 根据我使用的模块系统来编译TypeScript,我发现可以使用momentJs的方式有所不同。 当使用commonJs编译TypeScript时,一切都按预期工作,我可以按照momentJs文档进行操作:

import moment = require("moment");
moment(new Date()); //this works

如果我在导入“moment”时使用 TypeScript 模块系统“system”,则我必须执行以下操作。
import moment = require("moment");
moment.default(new Date()); //this works
moment(new Date()); //this doesn't work

我发现了一个解决方法,可以使它们两个在不考虑使用typescript模块系统的情况下都能正常工作。
import m = require("moment")
var moment : moment.MomentStatic;
moment = (m as any).default || m;

我不喜欢这个,我想知道它为什么会这样。是我做错了什么吗?有人能给我解释一下发生了什么吗?


1
从'moment'导入moment似乎可以工作,尽管编译器显示“没有默认导出”的错误(但仍然编译)。此外,请参阅此讨论:https://github.com/Microsoft/TypeScript/issues/2242#issuecomment-83694181 - Arlo
1
此外,这篇博客文章非常有用:http://www.jbrantly.com/es6-modules-with-typescript-and-webpack - Arlo
无论我如何导入,moment.default 都不存在,因此我实际上不知道如何创建 moment 对象的实例。有人有更新的答案吗? - adrian h.
你找到了合适的解决方案吗?我在使用SystemJS模块系统时遇到了同样的问题... - dror
我还在使用临时解决方法 :( - nemenos
你尝试过使用 import { moment } from 'moment' 吗?这样可以消除“没有默认导出”的错误,并且可能也能正常工作。 - Nypan
9个回答

31

我通过替换解决了这个问题。

import moment from "moment";

使用import语句

import * as moment from "moment";
这个。

21
这是因为SystemJS自动将moment转换为ES6样式的模块,通过将其包装在一个模块对象中,而CommonJS不会这样做。当CommonJS引入moment时,我们得到的是实际的moment函数。这是我们在JavaScript中已经做了一段时间的事情,应该非常熟悉。就像你写的一样:
var moment = function moment() {/*implementation*/}

SystemJS 导入 moment 时,它不会直接给你 moment 函数。相反,它会创建一个对象,并将 moment 函数分配给名为 default 的属性。这就好像你编写了以下代码:

var moment = {
    default: function moment() {/*implementation*/}
}
因为根据ES6/TS的规定,模块应该是一个或多个属性的映射,而不是函数,所以它才会这样做。在ES6中,曾经导出自身的大型外部库的惯例是使用export default将自身导出到模块对象的default属性下(请在此阅读更多信息:这里)。在ES6/TypeScript中,您可以使用简洁的import moment from "moment"语法导入函数。

你并没有做错任何事情,你只需要选择导入模块的格式,并坚持你的选择。如果你想同时使用CommonJS和SystemJS,你可以考虑配置它们使用相同的导入风格。搜索“systemjs default import”会引导你到针对你的问题的这个讨论,其中他们实现了--allowSyntheticDefaultImports设置。


1
非常好,感谢您详细而清晰的解释。这真的值得奖励! - ColinE
1
--allowSyntheticDefaultImports 已经在 1.8 版本中实现,该版本目前处于测试阶段。 - nemenos

5
我做了以下事情:
我按照以下方式安装了 moment定义文件
tsd install moment --save

然后我创建了main.ts:
///<reference path="typings/moment/moment.d.ts" />

import moment = require("moment");
moment(new Date());

我奔跑了起来:

$ tsc --module system --target es5 main.ts # no error 
$ tsc --module commonjs --target es5 main.ts # no error 

main.js看起来像这样:

// https://github.com/ModuleLoader/es6-module-loader/blob/v0.17.0/docs/system-register.md - this is the corresponding doc
///<reference path="typings/moment/moment.d.ts" />
System.register(["moment"], function(exports_1) {
    var moment;
    return {
        setters:[
            function (moment_1) {
                // You can place `debugger;` command to debug the issue
                // "PLACE XY"
                moment = moment_1;
            }],
        execute: function() {
            moment(new Date());
        }
    }
});

我的TypeScript版本是1.6.2。

我发现:

Momentjs导出了一个函数(即_moment = utils_hooks__hooksutils_hooks__hooks是一个函数,这很明显)。

如果您在我上面标注为PLACE XY的地方设置断点,您会发现moment_1是一个对象!而不是一个函数。相关行:12

简而言之:总之,这个问题与TypeScript无关。问题在于systemjs没有保留momentjs导出函数的信息。Systemjs只是从模块中复制导出对象的属性(在JavaScript中,函数也是对象)。我猜您应该在systemjs存储库中提交问题以找出他们是否认为这是一个bug(或特性:))。


错误不是在编译时出现,而是在尝试使用它时出现。似乎使用系统会使你找到一个名为“default”的附加字段来使用。如果使用CommonJS,则moment.default未定义,必须直接使用moment。 - nemenos
1
谢谢,我尝试调试了您的问题,并相应地更新了我的答案。希望有所帮助。 - MartyIX
它不是复制属性,而是将 moment 包装在一个模块对象中。 - mk.
@mk。感谢您的澄清。 - MartyIX

4

以下是我在使用System.js和Typescript 1.7.5时的操作步骤:

import * as moment from "moment";
...
moment.utc(); // outputs 2015 (for now).

请注意,我使用的是 utc() 方法。我不能使用 moment() 方法,因为正如 mk. 所解释的那样,它被 System.js 转换为 moment.default()。当然,Definitely Typed 类型定义不包含 default 方法,所以为了避免编译错误,需要使用类似 moment["default"]() 这样的东西(我知道,很丑陋)。
接下来,我需要将以下内容添加到 System.js 配置中:
System.config({
  paths: {
    'moment': 'node_modules/moment/moment.js'
  }
});

在此之后,一切都很顺利。

我只是想说谢谢,我不使用SystemJS(我在tsconfig中使用CommonJS作为`module'),但将'moment'添加到我的tsconfig路径确实修复了我使用的库(react-day-picker)中遇到的问题(该库使用moment.js),非常感谢,最终因此使它工作了:DFYI; 我遇到的错误如下; 'MomentLocaleUtils.js:68 Uncaught TypeError: (0,_moment2.default)不是一个函数' - Sander Looijenga

2

Moment是我正在开发的项目中需要引入的一个痛点,但我们最终通过以下方式解决了这个问题:

import momentRef = require('moment');
var moment: moment.MomentStatic = momentRef;

感谢您的回答,但即使使用SystemJS作为模块系统,错误信息仍然是:“未捕获(在Promise中)类型错误:moment不是一个函数”,与以前相同。 - nemenos
1
抱歉它没有帮助到你......我错过了systemjs部分......我的解决方案正在利用commonjs。 - Brocco
@Brocco,你可以通过使用const moment: moment.MomentStatic = require("moment");或者import * as moment from "moment"来清理你的项目。 - mk.

1

0

我原本写的是:

import * as moment from 'moment';

我把所有我想要改的都改成了:

var date: moment = moment();

现在变成了:

var date: moment.Moment = moment();


0
如果您正在使用TypeScript,import * as moment from 'moment'是有效的,但在SonarQube中会出现严重错误,因为需要明确导入所需的特定成员。 根据更清晰的代码即更好的代码原则,您应该明确导入模块中要使用的内容。使用import *会导入模块中的所有内容,并可能使维护者感到困惑。同样地,export * from "module"会导入并重新导出模块中的所有内容,并可能使不仅维护者,而且模块的用户感到困惑。

0

对于我的 system.config:

paths: {
    'moment': 'node_modules/moment/moment.js'
},
packages: {
    app: {
        format: 'register',
        defaultExtension: 'js'
    }
}

在我的组件中导入momentjs时,我删除了*,我认为这会将moment.js文件中的代码视为多个对象。
更改:
import * as moment from 'moment';

至:

import moment from 'moment';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接