资讯专栏INFORMATION COLUMN

从 JavaScript 到 TypeScript - 模块化和构建

Jonathan Shieber / 2595人阅读

摘要:不过,相对于静态类型检查带来的好处,这些代价是值得的。当然少不了的模块化标准,虽然到目前为止和大部分浏览器都还不支持它。本身支持两种模块化方式,一种是对的模块的微小扩展,另一种是在发布之前本身模仿的命名空间。有一种情况例外。

TypeScript 带来的最大好处就是静态类型检查,所以在从 JavaScript 转向 TypeScript 之前,一定要认识到添加类型定义会带来额外的工作量,这是必要的代价。不过,相对于静态类型检查带来的好处,这些代价是值得的。当然,TypeScript 允许不定义类型或者将所有类型定义为 any,但如果这样做,TypeScript 带来的大部分静态检查功能都会失去作用,换言之,也就没必要使用 TypeScript 了。

模块化

在转换之前还要注意的一个问题就是模块化。早期的 JavaScript 代码基本上是每个 HTML 页面对应一个或几个 JavaScript 脚本,那时候的 JavaScript 代码中很少有模块化的概念。不过随着 Web 2.0 的兴起,大量的工作从后端移到前端,JavaScript 程序变得越来越复杂,模块化成为刚需,大量的模块化框架随之而来,其中比较有名的有 RequestJS 及其带来的 AMD 标准,还有 SeaJS 带来的 CMD 标准。而随着 Node.js 的兴起以及 JavaScript 的全栈化,又有了 CommonJS 标准。之后又出现了广为使用的 SystemJS。当然少不了 ES6 的模块化标准,虽然到目前为止 Node.js 和大部分浏览器都还不支持它。

TypeScript 本身支持两种模块化方式,一种是对 ES6 的模块的微小扩展,另一种是在 ES6 发布之前本身模仿 C# 的命名空间。大部分使用命令空间的场景都可以使用 ES6 模块化标准来代替。我们先来看一看两种模块化方式区别。

命名空间

使用命令空间写的 TS 脚本在转译成 JS 后,可以不使用任何模块加载框架,直接在页面中加载即可使用。不过很遗憾,这种方式转义出来的 JS 程序不能直接在 Node.js 中使用。因为 tsc 不为会命名空间形式的模块生成 modules.exports 对象以及 require 语句。

有一种情况例外。将所有 .ts 文件转译成一个 .js,假设叫 all.js,那么它可以通过 node all 来运行。这种情况下不需要任何模块的导入导出。

不过在浏览器环境中,严格的按照依赖顺序引入生成的 .js 文件是可行的。早期没有使用模块化的 JS 文件就可以使用“命名空间”形式的模块化写法,甚至可以将原来成百上千行的大型 JS 源文件,拆分成若干小的 TS 文件,再通过 tsc --outfile 输出单一 JS 文件来使用,这样既能实现模块化重构,又能不改变原有的 HTML(或其它动态页面文件)的代码。

还有一点需要注意的是,在指定生成单一输出文件的情况下,TypeScript 不会通过代码逻辑去检查模块间的依赖关系。默认情况下它会按文件名的字母序逐个转译 .ts 文件,除非源文件中通过 /// 明确指定了依赖项。

ES6 模块

在 TypeScript 使用 ES6 模块语法来实现模块化的情况下,tsc 允许通过 module 参数来指定生成的 .js 会应用于何种模块化框架,默认的是 commonjs,其它比较常用的还有 amdsystem 等。

显然,如果原来的 JS 程序使用了 AMD 框架,在转换成 TS 的时候,就可以使用 ES6 模块写法,并通过 tsc --module amd 来输出对应的 JS 文件,同样不需要修改原来的页面文件。

但是,如果原来的 JS 文件没有使用任何模块框架的情况下,转换为采用 ES6 模块写法的 TS 代码,在构建的时候就会麻烦一点。这种情况下即使构建成单一输出文件,仍然会需要模块化框架的支持,比如需要 AMD 的 definerequire,或者需要 System 的 API 支持。

为了避免引入模块化框架,可以考虑以 commonjs 标准输出 JS,然后通过 Webpack 来把所有生成的 JS 打包成单一文件。这里既然用到了 Webpack,构建配置就可以更灵活了,因为 Webpack 可以指定多个 entry,可以有多个输出,它会通过 import ... 转译成的 require(...) 自动检查依赖项。而且 Webpack 还可以使用 ts-loader 直接处理 .ts 文件而不需要先使用 tsc 来进行转译。如果在 TS 中用到了高版本 ECMAScript 语法,比如 async/await,还可以通过 babel-loader 来增加一层处理……非常灵活。

但这里往往会有一个问题,生成的 .js 中所有定义都不在全局范围,那么脚本引入网页之后,如何使用其中定义的内容?这需要借助全局对象 window——这里不需要考虑 Node.js 的全局对象 global,因为在 Node.js 下一般是采用模块化的方式引入,不需要向全局对象注入什么东西。

window 注入对象(或函数、值等)的方法也很简单,分两步:申明、赋值,比如:

import MyApi from "./myapi";

declare global {
    interface Window {
        mime: MyApi;
    }
}

window.mime = new MyApi();
常用的构建配置

我们早期项目中使用 TypeScript 的命名空间,不过最近几乎都重构成 ES6 模块方式了。由于会用到 async 函数,所以一般会配置 TypeScript 输出 ES2017 代码,再通过 Babel 转译成 ES5 代码,最后由 Webpack 打包输出。

tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es2017",
        "lib": [
            "dom",
            "es6",
            "dom.iterable",
            "scripthost",
            "es2017"
        ],
        "noImplicitAny": false,
        "sourceMap": false
    }
}

targetes5es6 的时候,TypeScript 会有默认的 lib 列表,这在官方文档中有详细说明。target 定义为 es2017 是为了支持 async 函数,但这个配置没有默认 lib 列表,所以参考官方文档对 --target es6 使用的 lib 列表,补充 es2017 类型库即可。

webpack.config.js

这里使用了 Webpack2 的配置格式。

module.exports = {
    entry: {
        index: "./js/index"
    },
    output: {
        filename: "[name].js"
    },
    devtool: "source-map",
    resolve: {
        extensions: [".ts"]
    },
    module: {
        rules: [
            {
                test: /.ts$/,
                use: [
                    {
                        loader: "babel-loader",
                        options: {
                            presets: ["es2015", "stage-3"]
                        }
                    },
                    "ts-loader"
                ],
                exclude: /node_modules/
            }
        ]
    }
};
gulp task

如果还使用 gulp,任务是这样写的

const gulp = require("gulp");
const gutil = require("gulp-util");

// 转译JavaScript
gulp.task("webpack", () => {
    const webpack = require("webpack-stream");
    const config = require("./webpack.config.js");
    return gulp.src("./js/**/*.ts")
        .pipe(webpack(config, require("webpack")))
        .on("error", function(err) {
            gutil.log(err);
            this.emit("end");
        })
        .pipe(gulp.dest("../www/js"));
});

这里需要注意的是 webpack-stream 默认使用的是 webpack1,而我们的配置需要 webpack2,所以为它指定第二个参数,一个特定版本的 webpack 实例 (由 require("webpack") 导入的)。

需要的 Node 模块

从上面的构建配置中不难总结出构建过程需要安装的 Node 模块,有这样一些

gulp

gulp-util

webpack-stream

webpack

ts-loader

typescript

babel-loader

babel-core

babel-preset-es2015

babel-preset-stage-3

在 Node.js 环境直接运行 .ts

在 Node.js 中可以通过 ts-node 包来直接运行 TypeScript 代码。需要做的只是在入口代码文件(当然是个 .js 代码)中添加一句

require("ts-node").register({ /* options */ })

或者

require("ts-node/register")

因为 Node.js 7.6 开始已经直接支持 async 函数语法,所以即使用到了这个语法,也不用担心 ts-node 在内存的转译结果不能运行。

入口文件仍然必须是 .js 文件,这是个小小的遗憾,不过对于使用 Node.js 写构建脚本的用户来说,有两个好消息:gulp 和 webpack 都直接支持 .ts 入口(或配置)文件。比如以 gulp 为例,可以定义 gulpfile.ts (注意扩展名是 .ts) 如下

import * as gulp from "gulp";

gulp.task("hello", () => {
    console.log("hello gulp");
});

不过 gulp 也是通过 ts-node 模块来实现使用 TypeScript 的,而 ts-node 的功能依赖于 typescript,所以别忘了安装这两个模块。

扩展阅读

从 JavaScript 到 TypeScript - 声明类型

从 JavaScript 到 TypeScript - 泛型

从 JavaScript 到 TypeScript - 接口

关注作者的公众号“边城客栈” →

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82387.html

相关文章

  • webpack实战

    摘要:和类似的预处理器还有等。的用处非常多,包括给自动加前缀使用下一代语法等,目前越来越多的人开始用它,它很可能会成为预处理器的最终赢家。 webpack实战 查看所有文档页面:全栈开发,获取更多信息。快马加鞭,加班加点,终于把这个文档整理出来了,顺便深入地学习一番,巩固知识,就是太累人,影响睡眠时间和质量。极客就是想要把事情做到极致,开始了就必须到达终点。 原文链接:webpack实战,原...

    cyrils 评论0 收藏0
  • 使用 TypeScript 改造构建工具及测试用例

    摘要:第一个完全使用重构的纯项目已经上线并稳定运行了。测试用例的改造前边的改为大多数原因是因为强迫症所致。但是测试用例的改造则是一个能极大提高效率的操作。 最近的一段时间一直在搞TypeScript,一个巨硬出品、赋予JavaScript语言静态类型和编译的语言。 第一个完全使用TypeScript重构的纯Node.js项目已经上线并稳定运行了。 第二个前后端的项目目前也在重构中,关于前...

    Cristic 评论0 收藏0
  • [译]学习如何去学习 JavaScript - 5 个你应该如何花在学习 JS 上时间的建议

    摘要:拥抱异步编程纵观发展史也可以说成开发的发展史,你会发现异步彻底改变了这场游戏。可以这么说,异步编程已成为开发的根基。这也是你应尽早在上投入大量时间的一处核心知识点,这其中包含和等重要概念。这也是最突出的一项贡献。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...

    wanglu1209 评论0 收藏0
  • 如何使用TypeScriptWebpack编写网页应用

    摘要:所以,如果你也考虑开始使用,不妨也看一下。使用模块中,模块的使用方法与一致。安装好定义文件之后,如果使用等对支持较好的编辑器,则会提供更加强大的代码提示功能。如果使用配合的,则可以方便地构建浏览器可以运行的代码。 TypeScript所做的,是在JavaScript的基础上加入了类型,TypeScript编译器将TypeScript编译成JavaScript,可以在浏览器或者nodej...

    Codeing_ls 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<