{T}

项目创建与环境搭建

创建项目

bash
mkdir my-koa-project
cd my-koa-project
npm init -y
npm install koa
npm i koa-router

创建 index.js 文件

javascript
const Koa = require("koa")
const app = new Koa()

app.use(async (ctx, next) => {
  ctx.body = "Hello World"
})

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

添加 koa-router 路由

javascript
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

router.get("/api", async (ctx, next) => {
  ctx.body = "Hello api"
})

// allowedMethods() 方法用于根据 ctx.status 设置 response 响应头中的 Allow 字段,表示服务器支持的 HTTP 方法
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

koa-body、@koa/cors

bash
npm install koa-body
npm install @koa/cors

使用 插件

javascript
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()

const { koaBody } = require("koa-body")
const cors = require("@koa/cors")

router.prefix("/v1")

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

app.use(koaBody())
app.use(cors())

app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

koa-json

koa-json 是一个中间件,用于在 Koa 应用中自动格式化 JSON 响应。它可以美化输出的 JSON 数据,使其更具可读性

默认美化 josn 输出

javascript
const json = require('koa-json')
const Koa = require('koa')
const app = new Koa()

app.use(json())

app.use((ctx) => {
  ctx.body = { foo: 'bar' }
})

// @request: GET /
//
// @response:
// {
//   "foo": "bar"
// }

配置

javascript
const Koa = require('koa')
const app = new Koa()

app.use(json({ pretty: false, param: 'pretty' }))

app.use((ctx) => {
  ctx.body = { foo: 'bar' }
})

// @request: GET /
//
// @response:
// {"foo":"bar"}

// @request: GET /?pretty
//
// @response:
// {
//   "foo": "bar"
// }

koa-compose

koa-compose 是一个用于将多个中间件函数组合成一个单一可调用函数的工具,在 Koa 应用程序中非常有用。通过使用 koa-compose,你可以将多个中间件组织成链式调用,从而提高代码的可读性和可维护性。

bash
npm install koa-compose
yarn add koa-compose

没有使用 koa-compose

javascript
import Koa from "koa"
import Router from "koa-router"

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

import { koaBody } from "koa-body"
import cors from "@koa/cors"

router.prefix("/v1")

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

router.get("/api", async (ctx, next) => {
  ctx.body = "Hello api"
})


app.use(koaBody())
app.use(cors())

// allowedMethods() 方法用于根据 ctx.status 设置 response 响应头中的 Allow 字段,表示服务器支持的 HTTP 方法
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

使用 koa-compose

javascript
import Koa from "koa"
import Router from "koa-router"
import compose from "koa-compose"

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

import { koaBody } from "koa-body"
import cors from "@koa/cors"

router.prefix("/v1")

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

router.get("/api", async (ctx, next) => {
  ctx.body = "Hello api"
})

const middleware = compose([koaBody(), cors()])

app.use(middleware)

// allowedMethods() 方法用于根据 ctx.status 设置 response 响应头中的 Allow 字段,表示服务器支持的 HTTP 方法
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

Koa-compress

koa-compress 是一个用于在 Koa 应用中启用 HTTP 压缩(例如 gzip、deflate、brotli 等)的中间件。它可以显著减少响应体的大小,从而提高应用的性能,特别是在网络带宽有限的情况下

code
npm install koa-compress

以下是一个使用 koa-compress 的简单示例:

javascript
const Koa = require('koa');
const compress = require('koa-compress');

const app = new Koa();

// 默认配置
app.use(compress());

app.use(ctx => {
  ctx.body = 'Hello, World!';
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

配置选项

koa-compress 提供了一些配置选项,让你可以自定义压缩行为。以下是一些常用选项:

  • filter:指定哪些响应应该被压缩。默认只压缩符合特定类型(如文本)的响应
  • threshold:指定响应体的最小字节数,只有大于此阈值的响应才会被压缩。默认值为 1024(即 1KB)
  • gzip:启用或禁用 gzip 压缩,默认启用
  • deflate:启用或禁用 deflate 压缩,默认禁用
  • br:启用或禁用 Brotli 压缩,默认禁用

以下是一个带有自定义配置的示例:

javascript
const Koa = require('koa');
const compress = require('koa-compress');

const app = new Koa();

// 自定义配置
app.use(compress({
  filter(content_type) {
    return /text/i.test(content_type);
  },
  threshold: 2048, // 2KB
  gzip: {
    flush: require('zlib').constants.Z_SYNC_FLUSH
  },
  deflate: {
    flush: require('zlib').constants.Z_SYNC_FLUSH,
  },
  br: false, // 禁用 Brotli 压缩
}));

app.use(ctx => {
  ctx.body = 'Hello, World!';
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

配置选项详细说明

  • filter

    • 类型:function
    • 作用:指定哪些响应内容类型应被压缩。默认值是一个函数,压缩所有类型
    • 示例:filter: content_type => /text/i.test(content_type)
  • threshold

    • 类型:number
    • 作用:只有响应体大小大于此值的响应才会被压缩。默认值为 1024 字节
    • 示例:threshold: 2048 (只压缩大于 2KB 的响应)
  • gzip

    • 类型:object|boolean
    • 作用:启用或配置 gzip 压缩。设置为 false 禁用 gzip
    • 示例:gzip: { flush: require('zlib').constants.Z_SYNC_FLUSH }
  • deflate

    • 类型:object|boolean
    • 作用:启用或配置 deflate 压缩。设置为 false 禁用 deflate
    • 示例:deflate: { flush: require('zlib').constants.Z_SYNC_FLUSH }
  • br

    • 类型:object|boolean
    • 作用:启用或配置 Brotli 压缩。设置为 false 禁用 Brotli
    • 示例:br: { flush: require('zlib').constants.BROTLI_OPERATION_FLUSH }

Brotli 压缩

如果需要启用 Brotli 压缩,需要确保 Node.js 版本支持 Brotli(Node.js v11.7.0 及以上版本支持)。可以通过以下方式启用:

javascript
const Koa = require('koa');
const compress = require('koa-compress');

const app = new Koa();

app.use(compress({
  br: {
    params: {
      [require('zlib').constants.BROTLI_PARAM_QUALITY]: 4,
    },
  },
}));

app.use(ctx => {
  ctx.body = 'Hello, World!';
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

配置 Babel

Babel 可以帮助你在 Node.js 环境中使用最新的 JavaScript 特性。安装必要的 Babel 包:

bash
npm install @babel/core @babel/node @babel/preset-env

然后在项目根目录下创建一个 .babelrc 文件,配置 Babel:

json
{
  "presets": ["@babel/preset-env"]
}

设置 Nodemon

Nodemon 可以帮助你在文件变化时自动重启你的 Koa 服务器。安装 Nodemon:

bash
npm install nodemon -D

然后在 package.json 中配置脚本来使用 Babel 和 Nodemon:

json
"scripts": {
  "start": "nodemon --exec babel-node index.js"
}

设置环境变量

code
npm install cross-env -D

安装 webpack

在一个 Koa2 项目中安装 Webpack 通常是为了处理前端资源的构建和打包。虽然 Koa 是一个后端框架,它也可以用来构建全栈应用,包括前端部分。在这种情况下,Webpack 可以用于以下目的:

  1. 打包前端资源:将 JavaScript、CSS、图片等前端资源打包成一个或多个文件,优化加载速度和性能
  2. 处理现代 JavaScript 语法:通过 Babel 等加载器(loader),Webpack 可以将现代 JavaScript 语法(如 ES6、ES7)转译成兼容性更好的语法,以支持更多的浏览器
  3. 模块化开发:支持模块化开发,允许你使用 importexport 语法将代码分割成多个模块,增强代码的可维护性和可读性
  4. 热模块替换(HMR):提高开发效率,在修改代码时无需刷新浏览器,立即看到更改的效果
  5. 优化和压缩:通过插件和加载器,Webpack 可以优化和压缩代码,减少文件大小,提高加载速度

下面是一个示例,说明如何在 Koa2 项目中配置 Webpack 来处理前端资源

bash
npm i webpack webpack-cli -D

npm i clean-webpack-plugin webpack-node-externals babel-loader -D

webpack.config.js

node: { __dirname: true, __filename: true } 的意思是告诉 Webpack 在构建过程中保留 __dirname__filename 的原始值,不对它们进行替换

javascript
const path = require("path")
const nodeExternals = require("webpack-node-externals")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

const webpackConfig = {
  target: "node",
  mode: "development",
  entry: {
    server: path.join(__dirname, "./src/index.js")
  },
  output: {
    path: path.join(__dirname, "./dist"),
    filename: "[name].bundle.js"
  },
  devtool: "eval-source-map", // "eval-source-map
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: "babel-loader"
        },
        exclude: [path.join(__dirname, "/node_modules")]
      }
    ]
  },
  externals: [nodeExternals()],
  plugins: [new CleanWebpackPlugin()],
  node: {
    __dirname: true,
    __filename: true
  }
}
module.exports = webpackConfig

执行命名编译项目

code
npx webpack

将 commonjs 转为 es6 语法

javascript
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()

const { koaBody } = require("koa-body")
const cors = require("@koa/cors")

router.prefix("/v1")

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

router.get("/api", async (ctx, next) => {
  ctx.body = "Hello api"
})

app.use(koaBody())
app.use(cors())

// allowedMethods() 方法用于根据 ctx.status 设置 response 响应头中的 Allow 字段,表示服务器支持的 HTTP 方法
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

修改完成后

javascript
import Koa from "koa"
import Router from "koa-router"
const app = new Koa()
const router = new Router()

import { koaBody } from "koa-body"
import cors from "@koa/cors"

router.prefix("/v1")

router.get("/", async (ctx, next) => {
  ctx.body = "Hello World"
})

router.get("/api", async (ctx, next) => {
  ctx.body = "Hello api"
})

app.use(koaBody())
app.use(cors())

// allowedMethods() 方法用于根据 ctx.status 设置 response 响应头中的 Allow 字段,表示服务器支持的 HTTP 方法
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000")
})

执行命令从 node src/index.js 转变为 babel-node src/index.js

bash
babel-node src/index.js

修改 package.json 命令

json
"scripts": {
  "start": "nodemon --exec babel-node src/index.js"
}

优化 webpack 配置

Webpack EnvironmentPlugin

EnvironmentPlugin 是 Webpack 内置的一个插件,它可以简化将环境变量传递给前端代码的过程。这个插件通过将指定的环境变量注入到代码中,使得在代码中可以访问这些环境变量

javascript
const webpack = require('webpack');

module.exports = {
  // 其他配置项
  plugins: [
    new webpack.EnvironmentPlugin(["NODE_ENV"])
  ]
};

然后在源代码中,可以这样使用:

javascript
if (process.env.NODE_ENV === 'production') {
  // 生产环境的配置或逻辑
} else {
  // 非生产环境的配置或逻辑
}

你也可以为环境变量提供默认值,如果 NODE_ENV 没有定义,process.env.NODE_ENV 将被设置为 'development'

javascript
new webpack.EnvironmentPlugin({
  NODE_ENV: 'development', // 默认值
  DEBUG: false
})

terser-webpack-plugin

terser-webpack-plugin 是一个用于优化和压缩 JavaScript 代码的 Webpack 插件。它使用 Terser(一个快速、现代化的 JavaScript 压缩器)来减少输出文件的体积,从而提高页面加载速度和性能。

bash
npm install terser-webpack-plugin -D

在 Webpack 配置中使用 terser-webpack-plugin 来优化和压缩输出的 JavaScript 文件:

javascript
const TerserPlugin = require('terser-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

配置选项

terser-webpack-plugin 提供了多种配置选项,让你可以控制压缩过程的细节。以下是一些常用选项:

  • compress:控制压缩行为的选项,例如 drop_console 可以去除所有的 console 语句

  • mangle:控制变量名混淆。如果设置为 true,Terser 将混淆变量名以减少代码体积

  • output:控制输出行为的选项。例如,comments: false 可以去除所有注释

  • extractComments:控制是否将注释提取到单独的文件。如果设置为 false,则不会提取注释

为了在压缩后的代码中保留 Source Maps,可以配置 terser-webpack-plugin 来生成 Source Maps:

javascript
const TerserPlugin = require('terser-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({
      terserOptions: {
        compress: {
          drop_console: true, // 去除 console 语句
        },
        mangle: true, // 混淆变量名
        output: {
          comments: false, // 去除注释
        },
      },
      extractComments: false, // 是否提取注释到单独文件
      sourceMap: true, // 保留 Source Maps
    })],
  },
};

多个压缩插件

可以在 optimization.minimizer 数组中使用多个压缩插件。例如,同时使用 terser-webpack-plugincss-minimizer-webpack-plugin 来压缩 JavaScript 和 CSS:

javascript
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin(),
    ],
  },
};

webpack-merge

webpack-merge 是一个用于合并多个 Webpack 配置对象的工具。它在管理 Webpack 配置时非常有用,尤其是当你需要根据不同的环境(如开发环境和生产环境)进行不同配置时

webpack-merge 可以合并多个 Webpack 配置对象

bash
npm install webpack-merge -D

webpack.config.base.js

javascript
const path = require("path")
const webpack = require("webpack")
const nodeExternals = require("webpack-node-externals")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

const webpackConfig = {
  target: "node",
  entry: {
    server: path.join(__dirname, "./src/index.js")
  },
  output: {
    path: path.join(__dirname, "./dist"),
    filename: "[name].bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: "babel-loader"
        },
        exclude: [path.join(__dirname, "/node_modules")]
      }
    ]
  },
  externals: [nodeExternals()],
  plugins: [new CleanWebpackPlugin(), new webpack.EnvironmentPlugin(["NODE_ENV"])],
  node: {
    __dirname: true,
    __filename: true
  }
}
module.exports = webpackConfig

webpack.config.dev.js

javascript
const { merge } = require("webpack-merge")
const NodemonPlugin = require("nodemon-webpack-plugin")

const baseWebpackConfig = require("./webpack.config.base")

const webpackConfig = merge(baseWebpackConfig, {
  mode: "development",
  devtool: "eval-source-map",
  stats: { children: false }, // Hide children information
  plugins: [new NodemonPlugin()]
})

module.exports = webpackConfig

webpack.config.prod.js

javascript
const { merge } = require("webpack-merge")
const baseWebpackConfig = require("./webpack.config.base")

const TerserWebpackPlugin = require("terser-webpack-plugin")

const webpackConfig = merge(baseWebpackConfig, {
  mode: "production",
  stats: { children: false, warnings: false },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin({
        terserOptions: {
          warnings: false,
          compress: {
            warnings: false,
            // 是否注释掉console
            drop_console: false,
            dead_code: true,
            drop_debugger: true
          },
          output: {
            comments: false,
            beautify: false
          },
          mangle: true
        },
        parallel: true
      })
    ],
    splitChunks: {
      cacheGroups: {
        commons: {
          name: "commons",
          chunks: "initial",
          minChunks: 3,
          enforce: true
        }
      }
    }
  }
})

module.exports = webpackConfig

添加脚本

package.json 中添加脚本以运行不同的配置:

json
{
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --config config/webpack.config.prod.js",
    "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.config.prod.js",
  }
}

splitChunks

splitChunks 是 Webpack 4 引入的一个特性,用于提取和优化代码分割。它允许你将公共模块提取到单独的文件中,从而实现更好的缓存和更快的加载速度

javascript
splitChunks: {
  cacheGroups: {
    commons: {
      name: "commons",
      chunks: "initial",
      minChunks: 3,
      enforce: true
    }
  }
}

cacheGroupssplitChunks 的一个属性,用于定义特定的分割策略。你可以在 cacheGroups 中定义多个分割策略。每个策略都是一个命名的组。

配置中定义了一个名为 commons 的缓存组

  • name: "commons": 提取的公共模块将被放到一个名为 commons.js 的文件中

  • chunks: "initial":

    • 指定在哪些块之间共享模块
    • "initial" 表示只选择初始块(initial chunks)
    • 其他选项包括 "async"(仅选择异步加载的块)和 "all"(选择所有块)
  • minChunks: 3:

    • 指定至少被多少个块共享的模块才会被提取到这个缓存组
    • 在这个例子中,只有被至少三个不同块引用的模块才会被提取到 commons.js
  • enforce: true:

    • 强制执行这个缓存组的规则,忽略其他设置
    • 即使不满足 splitChunks.minSize, splitChunks.maxAsyncRequests, splitChunks.maxInitialRequests 等全局选项,仍然会执行此分割策略

作用

这段配置的主要作用是:

  1. 提取公共模块:它将那些被至少三个不同入口文件引用的模块提取到一个单独的 commons.js 文件中

  2. 提升加载效率:减少初始加载的体积,提高加载效率,因为公共模块会被浏览器缓存,只需加载一次

  3. 优化缓存:通过将不常变化的代码提取到公共模块中,优化了缓存策略,使得后续页面加载更快

假设你有三个入口文件 entry1.js, entry2.js, 和 entry3.js,并且它们都引用了同一个库 commonLib.js 。在这种情况下,commonLib.js 会被提取到 commons.js 中,因为它被三个不同的入口文件引用