webpack, babel, eslint,typescript,koa服务器应用简单入门配置

1,996 阅读7分钟

环境:

条目版本
osWindows 10 20H2 Enterprise
node -vv14.15.0
npm -v6.14.8
vscodev1.51.1(20-11-10)

这是一份学习笔记

一、项目创建

按照下面的几个步骤,创建 nodejs 项目

mkdir my-demo
cd .\my-demo
npm init -y
# -y 参数用于使用缺省配置,而跳过不断的回车

目前,我配置的目录结构如下

+ src
  - index.ts
- package.json

我们的项目的开发语言是 typescript,既然如此,至少我们需要先安装好基础需要的包

npm install --save-dev typescript
# 然后,让 tsc 给我们生成一个初始配置文件
npx tsc --init
# npx 可以让我们直接运行 node_modules/.bin/ 目录下的脚本文件

然后调整以下配置, 在 compilerOptions 下,target 指定编译出的 js 代码的版本标准

小谈 target 版本设置:

查看支持情况,Node.js ES2015/ES6, ES2016…

可以得知 es2018 标准在 node v10.3.0 下就已经支持得很是完整了,es2019 在 node v12.4.0 下完整支持,es2020 在 node v14.5.0 下已经完整支持。这年头,不要告诉我,你的服务器环境还是 node v8.x ?

有些伙伴喜欢调成 es5,只不过我个人觉得没有必要。因为编译器会生成许多兼容代码来让软件可以运行与低版本的 node 环境,这些兼容代码会影响代码性能,而且 node 在支持 新的语法的时候是做过优化的。我的环境一般是 > 12.x ,所以用了 es2019

outDir是编译输出目录

isolatedModules 是保证每个文件都是一个独立模块

Strict Type-Checking Options 下的所有配置都直接勾选,都用了 typescript 了,还要啥 any ?

而与 compilerOptions 平行的 include 就是指的源代码路径,src/**/* 就是 src 目录下的所有文件

{
  "compilerOptions": {
    "target": "es2019",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "outDir": "./dist",                       /* Redirect output structure to the directory. */
    "removeComments": true,                /* Do not emit comments to output. */
    "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

      /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    "noImplicitAny": true,                    /* Raise error on expressions and declarations with an implied 'any' type. */
    "strictNullChecks": true,              /* Enable strict null checks. */
    "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
  },
  "include": ["src/**/*"]
}

至于其他配置,暂且就默认吧。现在,我们已经能用 tsc 了,算是搭建了一个基础环境。

二、使用 typescript 编写一个 koa 框架的 hello world

要使用 koa 框架编写一个服务端程序的话,首先需要的是安装必要的 koa 相关依赖

npm install --save koa koa-body @koa/router
# 由于我们使用的是 typescript, 所以还需要安装 typescript 以及 Types 类型定义的包
npm install --save-dev @types/koa @types/koa__router

然后在 src/index.ts 中插入一个 hello world 程序:

// src/index.ts
import Koa from 'koa';
import koaBody from 'koa-body';
import Router from "@koa/router";

const app = new Koa();
const router  = new Router();

app.use(koaBody());

router.get('/', async (ctx) => {
  ctx.body = { hello: 'world' }
})

app.use(router.routes());

const port = 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

export default app;

现在我们来试着编译一下

npx tsc
# 如果没出错的话继续运行
node dist/index.js

可以发现,编译成功

进入浏览器访问一下,已经有一个 json 字符串了(有格式是因为我装了插件)

这一步结束,咱们的项目已经能够跑起来了。

三、babel 配置 typescript

# 安装 babel 相关必要的依赖
npm install --save-dev @babel/core @babel/cli @babel/preset-env

# 安装 babel 解析 typescript 要用到的相关依赖
npm install --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-typescript

安装完成之后,项目根目录创建 babel 配置文件 babel.config.json(也可以是   .js, .cjs, .mjs 后缀,但这都不重要,内容是一样的就行) ,并编写如下配置内容:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": true
        }
      }
    ],
    "@babel/preset-typescript"
  ],
  "plugins": [
    "@babel/proposal-class-properties",
    "@babel/proposal-object-rest-spread"
  ]
}

上面的 json 文件内容,主要就是添加了两个预设配置,和两个插件。

@babel/preset-env 是 babel 官方准备的,设置的时候给个参数,让它知道代码运行于 node 环境

@babel/preset-typescript 也是官方的 : ) ,调用 tsc 解析 typescript

@babel/proposal-class-properties 支持类属性,这个特性目前好像是 es 2021 stage 3 吧,我也不是很清楚

@babel/proposal-object-rest-spread 支持对象展开符

然后在命令行先删除 dist 目录再编译,测试一下:

rmdir dist -r
npx babel src --out-dir dist --extensions ".ts"

运行成功。每次需要编译代码的时候就运行一遍上面的 npx babel xx 就好了。但...这么麻烦的参数,就不能简单一点么?

很遗憾的是,babel 的配置文件里并没有提供与之前 tsconfig.json 中的 includecompilerOptions.outDir 功能相似的配置项。所以你每次都要指定 --out-dir--entensions 参数。

除了每次在命令行指定参数,就没有别的办法来简化命令行参数了么?

当然有,我们可以把这一长串几乎不会变化的命令写到 pacakge.json中的 scripts项目中,比如:

{
  "scripts": {
    "babel:build": "npx babel src --out-dir dist --extensions \".ts\"",
    "start": "node dist/index.js",
    "babel:dev": "npm run babel:build && npm run start"
  },
}

之后,我们就可以使用 npm run babel:build 来编译,使用 npm run start 来启动服务器,使用 npm run babel:dev 来组合上述两条命令。

备注:

babel 监听文件修改,加入 --watch 参数

例如:

# https://babeljs.io/docs/en/babel-cli#compile-files
npx babel src --watch --out-dir dist --extensions ".ts"

node 监听文件修改,添加依赖 nodemon,然后命令行

# https://www.npmjs.com/package/nodemon
nodemon dist/index.js

目前,如果需要同时监听 ts 源码修改并及时刷新服务端,可以开两个命令行,一遍各运行一个,或者是用 npm-run-all - npm (npmjs.com)),使两个命令并行执行,不过,这里就不用介绍了。

这一节结束,我们的项目已经能够基于 babel 走向简单的工程化了。

四、让 webpack 来调用 babel 执行转译

webpack 在前端行业里真的真的太热门了,我断断续续了解过一些,今天也在摸索方法。

首先我们安装一些依赖

# webpack 核心依赖
npm install --save-dev webpack webpack-cli
# 让 webpack 能调用 babel 的依赖插件
npm install --save-dev babel-loader
# 其他一些辅助的,但特别有用的依赖
npm install --save-dev externals-dependencies clean-webpack-plugin
# 可选依赖,老实说,此纯后端项目没用上,但是它对于一些前端项目是很有用的
npm install --save-dev webpack-dev-server

让我们来开始写配置文件,在项目根目录创建 webpack.config.js

const path = require('path');
const externals = require('externals-dependencies');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: './src/index.ts', // 项目入口文件,之后可以设置多入口
  module: {
    rules: [
      {
        // 设定转译规则,.ts, .js 使用 babel-loader 把任务交给 babel
        test: /\.(ts|js)?$/,
        use: {
          loader: 'babel-loader',
          options: { cacheDirectory: true }, // 缓存
        },
        exclude: /node_modules/,   // 排除掉 node_modules 文件夹
      },
    ],
  },
  // 开发服务器,其实此项目没有用上,配置的含义是以 contentBase 为根目录启动一个服务器
  // 打开浏览器就可以访问该服务器,代码有更新会自动刷新页面,端口默认 8080
  devServer: {
    contentBase: './dist',
    open: true,
  },
  resolve: {
    extensions: ['.ts', '.js'],   // 设置一下 webpack 要扫描的文件后缀
  },
  plugins: [
    new CleanWebpackPlugin(),    // 用插件清理一下 webpack 生成的某些垃圾
  ],
  output: {
    filename: 'index.js',                 // 输出文件的文件名
    path: path.resolve(__dirname, 'dist'), // 输出文件所在路径,需要用绝对路径
  },
  target: 'node', // 服务端打包
  node: {
    global: true,
    __filename: true,
    __dirname: true,
  },
  externals: [externals()], // node 打包可去除一些警告
};

有些其他博客有用上了配置文件合并啥的功能插件,这儿目前先不考虑,而且还会增加对于新手入门的难度。

(明明是你自己不会)

现在编译运行就使用:

npx webpack
node ./dist/index.js

webpack 监听修改的方式:

npx webpack --watch

如果使用 webpack-dev-server, 就可以:

# 已经配置好了情况下
npx webpack-dev-server

OK。我们的项目向工程化的方向又进了一步。

五、使用 eslint 检查 typescript 项目

(问题引入)在上述的代码中我们已经能够使用 webpack 来编译项目了,现在,我们先启动 npx webpack --watch来监听我们的文件修改。

然后在代码的某处,添加一点点“错误”:

// 省略其他
const d: number = 'helloworld';
console.log(d);
// 省略其他

上面两句代码,我讲字符串赋值给了一个 number 类型的变量,回头检查一下控制台。

怎么回事,居然编译成功了?我不是用的 typescript 么,居然没有进行类型检查?如果没有进行类型检查,我要这 typescript 有何用?

紧接着去核查生成了代码,发现对应位置的代码如下:

console.log("helloworld")

这段 js 代码也完全没有任何问题。

那 ts 究竟做了什么?

原来是 babel 只负责了转译 ts 代码的任务,并没有进行类型检查。既然如此,我们还是需要单独配置一下类型检查。

第一个解决方式是,直接把 tsc 作为类型检查工具

先在 tsconfig.json添加如下配置,让 tsc 不要生成输出文件,也就是不要负责文件生成,而只负责检查类型。

{
  "compilerOptions": {
    "noEmit": true                        /* Do not emit outputs. */
  }
}

然后,运行类型检查就在命令行使用:

npx tsc -w

同时,也运行起 webpack 的监听服务,两个 watch 服务,一遍负责转译,一边负责类型检查。效果看起来就像下面这样。

我们自动检查类型并编译的目标,算是完成了一半。

第二个方法便是使用 eslint 了。

控制台中运行命令:

npm install --save-dev eslint
# 安装完成既初始化 eslint
npx eslint --init

执行 npx eslint --init 后的样子应该是这样:

我用的 vscode 编辑器,安装了 dbaeumer.vscode-eslint 插件。上面的任务一执行结束,回到 vscode 我就看到了许多报错提示,可见,其实 npx eslint --init已经帮我们做了一些配置。

module.exports = {
  env: {
    es2021: true,
    node: true,
  },
  extends: [
    'airbnb-base',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
  },
  plugins: [
    '@typescript-eslint',
  ],
  rules: {
  },
};

当然,如果你不用 npx eslint --init,也可以自己安装如下相关依赖,并写好上述配置文件。

devDependencies: {
    "eslint": "^7.13.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-plugin-import": "^2.22.1
    "@typescript-eslint/eslint-plugin": "^4.7.0",
    "@typescript-eslint/parser": "^4.7.0",",
}

但是这样还是不太够的,我们添加一些配置。添加后的配置文件如下:

module.exports = {
  env: {
    es2021: true,
    node: true,
  },
  extends: [
    'airbnb-base', // airbnb 的配置很受欢迎
    'plugin:@typescript-eslint/recommended', // 启动该插件的推荐配置
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
  },
  plugins: [
    '@typescript-eslint',
  ],
  rules: {
    'linebreak-style': 'off',
    'no-console': 'off',
    '@typescript-eslint/no-var-requires': 'off',
  },
};

保存后,在控制台执行

npx eslint src --ext .ts
# 或者 lint 某一些文件
npx eslint src/index.ts src/xxx.ts

接下来就可以输出有关 eslint 的错误信息了。类似于:

D:\DevDemo\demo\my-demo\src\index.ts
   3:20  error  Strings must use singlequote  quotes
   15:32  error  Missing semicolon             semi
   16:3   error  Missing semicolon             semi

回到 vscode,看一看代码里还是不是有很多很多关于换行符,console 之类的报错, require 符号之类的报错,如果还有,不妨在 vscode 中使用

ctrl + shift + p ,然后在弹出的输入框中输入reload window,按下回车,让 vscode 插件来重新加载 eslint 配置。

添加到 scripts

{
    "scripts": {
        "webpack:build": "webpack",
        "webpack:watch": "webpack --watch",
        "webpack:dev": "webpack && npm run start",
        "lint": "eslint src --ext .ts"
    }
}

我加了个 webpack 前缀,因为之前也配置了 babel 的相关任务。

OK,eslint 配置完成。

如果觉得有用的话,不妨动动鼠标点个赞?如果哪儿出现疏漏或是我没有理解到的错误,我非常感谢您的指正。