使用 Webpack 搭建前端 Typescript 模块化开发最小开发环境
前言
题中所说的的Typescript最小开发环境是指能够模块化开发Web前端所需的最少组件组成的开发环境,所谓的模块化开发就是能够在分文件的情况下进行模块的分割和整合。虽然在不借助 Webpack 的情况下可以使用 Typescript 自带 namespace
实现模块化的开发,但相比于使用 import
和 export
这两个关键字,使用 namespace
显得不那么的清晰。但这篇文章还是提供使用 namespace
实现模块化的方法。
使用 namespace
实现模块化
使用 namespace
实现模块化其实很简单,只要把一个 namespace
看作一个模块,把 /// <reference path="**">
看作 import
语句就可以了,但事实上 namespace
是一个多文件的模块,例如下面代码中的logger1.ts文件和logger2.ts文件可以看作同属于一个模块 Logger
,两个文件通过logger2.ts中的///<reference/>
连接起来。
// logger1.ts
namespace Logger {
export function info(info: any) {
console.log(info);
}
export function warn(warn: any) {
console.warn(warn);
}
}
// logger2.ts
/// <reference path="./logger1.ts"/>
namespace Logger {
export function error(error: any) {
console.error(error);
}
}
接着就是调用模块中的元素,由于logger2.ts中引用了logger.ts,所以在main.ts中只需要引用logger2.ts。
// main.ts
///<reference path="./logger2.ts"/>
Logger.info('Hello world');
然后就是使用tsc命令将三个文件编译成一个文件,命令如下
> tsc --outFile main.js ./main.ts
然后再文件夹内就有一个main.js文件,在*.html文件中引入这个js文件就能够完成功能的实现。
虽然可以使用多个文件来定义一个模块,但我觉得将一个文件作为单独一个模块更加易于管理,使用单文件模块可以这样改写上述代码
1.将logger2.ts重命名为error.ts, 文件中的代码更改为
// error.ts
namespace ErrorLogger {
export function error(error: any) {
console.error(error);
}
}
2.将logger1.ts重命名为logger.ts,文件中的代码更改为
// logger.ts
/// <reference path="./error.ts"/>
namespace Logger {
export function info(info: any) {
console.log(info);
}
export function warn(warn: any) {
console.warn(warn);
}
export let error = ErrorLogger.error;
}
3.更改main.ts的代码为
// main.ts
/// <reference path="logger.ts"/>
namespace Main{
Logger.error('error');
Logger.warn('warn');
Logger.info('info');
}
然后重新执行一遍 tsc
命令即可。
从这各部分可以学习到使用 Typescript 的 namespace
进行多文件模块和单文件模块的开发,虽然 namespace
可以实现模块化,但是官方也不推荐单纯的使用 namespace
来进行模块化开发,理由如下
但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中。
所以我么应该使用更加通用的模块化开发方案,那就是ES6中使用import/export关键字的ES Module方案。
Typescript 环境下的 ESModule 模块化开发
ESModule 就是ES6提出的模块化方案,Typescript 的模块化方案取自于ES6,所以只需要掌握ES6 模块化的用法就能够使用 Typescript 的模块化用法。使用 ESModule 改写上面的例子,可以得出以下的代码
// logger.ts
function info(info: String): void {
console.log(info);
}
function warn(warn: String): void {
console.warn(warn)
}
function error(error: String): void{
console.error(error);
}
export {info, warn, error};
// main.ts
import * as Logger from './logger';
Logger.error('Logger Error');
Logger.warn('Logger warn');
Logger.info('Logger info');
这样一看来跟使用 namespace
的差别不大,但其实在 Typescript 中使用 ESModule 的一个好处就是可以按需引入,例如在main.ts中我们只需要 info
方法,那么可以这样改写。
import {info} from './logger';
info('Logger info');
虽然 ESModule 很好,但是这种模块化的写法并不能在浏览器中使用,想要在浏览器中使用模块化只能通过各种打包工具来实现,在下面会介绍配置 Webpack 是的 Webpack 能够自动编译打包 Typescript 文件。
Webpack 配置
Webpack配置主要分为两个部分,分别是识别编译*.ts文件和编写build
脚本。首先来看最终效果。
最终效果
在这个例子中有两个文件,分别是time.ts和app.ts,其中app.ts是是入口,两个文件内的代码如下:
// timer.ts
export function timer() {
return new Date();
}
// main.ts
import {time} from './timer';
let timeEle = document.querySelector('#timer') as Element;
timeEle.innerHTML = time().toString();
然后执行npm run build
命令(npm run build
的脚本在package.json中定义,稍后会有介绍),然后目录下会多了一个/dist文件夹,下面有一个bundle.js文件,之后在*.html文件中引用这个bundle.js文件即可
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="timer"></div>
</body>
<script src="./dist/bundle.js"></script>
</html>
运行结果如下
初始化文件夹和引入webapck
首先需要使用终端输入npm init
初始化当前工作文件夹,文件夹中会多出一个package.json文件,整个文件用于配置npm目录,然后我们需要安装webpack,在终端中输入如下命令完成安装
> npm install --save-dev webpack
接着就是创建Typescript开发环境
创建Typescript开发环境
Typescript开发环境必须通过npm安装typescript模块,在终端中输入以下命令完成安装
> npm install typecript
然后安装webpack需要用到的ts-loader
> npm install --save-dev ts-loader
接着在当前目录创建一个webpack.conf.js配置文件夹,并将以下代码写入其中
const path = require('path');
module.exports = {
entry: './app.ts',
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
然后在终端输入命令
> webpack --config ./webpack.conf.js
打开index.html即可看到运行结果。
配置build脚本
上面我们使用了npm run build
的脚本,只需要子啊package.json的scripts
字段中加入下面的语句
"build": "webpack --config ./webpack.conf.js"
然后在终端中使用npm run build
即可完成打包功能。
缺点
使用webpack进行模块化开发的缺点主要是打包之后的代码的可阅读性很差,而使用namespace
编译后的代码可阅读性非常好。
webpack 打包后代码分析
首先 webpack 打包后的代码中,前面的代码是用于定义一个模块化系统,可以先不深入阅读,模块化系统实现的代码之后就是我们的代码,我们的代码被编译成字符串,使用eval
来执行,让人很难读下去。
// bundle.js
前面一段webpack用于实现模块化的代码,可以自行打开bundle.js中阅读
/***/ "./src/app.ts":
/*!********************!*\
!*** ./src/app.ts ***!
\********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nvar timer_1 = __webpack_require__(/*! ./timer */ \"./src/timer.ts\");\r\nvar timeEle = document.querySelector('#timer');\r\ntimeEle.innerHTML = timer_1.time().toString();\r\n\n\n//# sourceURL=webpack:///./src/app.ts?");
/***/ }),
/***/ "./src/timer.ts":
/*!**********************!*\
!*** ./src/timer.ts ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nfunction time() {\r\n return new Date();\r\n}\r\nexports.time = time;\r\n\n\n//# sourceURL=webpack:///./src/timer.ts?");
/***/ })
/******/ });
namespace
编译后代码分析
将原有的代码使用 namespace
语法改写之后再使用tsc --outFile bundle.js ./app.ts
命令编译之后的代码如下所示
var Timer;
(function (Timer) {
function time() {
return new Date();
}
Timer.time = time;
})(Timer || (Timer = {}));
///<reference path="./timer.ts"/>
var App;
(function (App) {
var timeEle = document.querySelector('#timer');
timeEle.innerHTML = Timer.time().toString();
})(App || (App = {}));
代码十分的工整,javascript功底比较好的同学应该很容易能够看懂。
总结
这篇文章主要是分析了Typescript中实现模块化的手段,介绍了在Typescript环境下使用namespace
和 ESModule 实现前端模块化开发的关键点,也介绍了使用 Webpack 配置 Typescript 开发环境的主要步骤,最后还分析了webpack打包后代码和tsc编译后代码的可读性。