基于Webpack/TypeScript/Koa的环境配置

栏目: Node.js · 发布时间: 5年前

内容简介:TypeScript是一种开源编程语言,在软件开发社区中越来越受欢迎。TypeScript带来了可选的静态类型检查以及最新的ECMAScript特性。 作为Javascript的超集,它的类型系统通过在键入时报告错误来加速和保障我们的开发,同时越来越多对的库或框架提供的通过

TypeScript是一种开源编程语言,在软件开发社区中越来越受欢迎。TypeScript带来了可选的静态类型检查以及最新的ECMAScript特性。 作为Javascript的超集,它的类型系统通过在键入时报告错误来加速和保障我们的开发,同时越来越多对的库或框架提供的 types 文件能够让这些库/框架的API一目了然。我对这门语言垂涎已久,但是迟迟无法找到练手的地方。 很显然的,个人博客又一次的成了我的学习试验田:smile_cat:。我放弃了上一版Vue单页面的框架,改为基于TypeScript/Koa的多页面应用。在改造的过程中,我试着将服务端(Koa)代码以及前端代码都使用TypeScript来开发,中间使用了webpack作为开发时前后端的桥梁。

目录结构

.
├── .babelrc
├── bin
│   ├── dev.server.ts
│   ├── pm2.json
│   └── app.ts
├── config                                       # 配置目录
│   ├── dev.ts
│   └── prod.ts
├── nodemon.json
├── package.json
├── postcss.config.js
├── scripts
│   └── webpack.config.js
├── src                                          # 源码
│   ├── assets                                   # 静态资源
│   │   ├── imgs
│   │   ├── scss
│   │   └── ts
│   ├── entries                                  # webpack入口
│   │   ├── blog.ts
│   │   └── index.ts
│   └── views                                    # 模板(文件名与入口一一对应)
│       ├── blog.html
│       ├── index.html
│       └── layout                               # 模板布局
│           ├── footer.html
│           └── header.html
├── server                                       # 服务端
│   ├── app.ts
│   └── middleware
│       └── webpack-dev-middleware.ts
├── test                                         # 单元测试
│   └── .gitkeep                                      
├── tsconfig.front.json
└── tsconfig.json
复制代码

安装项目依赖

npm i --save koa koa-{router,bodyparser,static,ejs}

npm i -D typescript ts-node nodemon @types/{node,koa,koa-router,koa-bodyparser}
复制代码

开发环境(development)流程

基于Webpack/TypeScript/Koa的环境配置

ts-node 启动项目后,整个流程分为两部分,蓝色线条的代表纯服务端代码的编译过程。服务端代码是纯 typeScript 文件,可以通过 ts-node 直接编译运行。前端代码包含了 ejs 渲染所需要的模板文件(html),以及模板中所引用的静态资源(ts, scss, img),这部分需要通过webpack来编译。

// path: bin/dev.server.ts
import webpack = require('webpack')
// 引入项目主模块
import app from '../server/app'
// webpack-dev-middleware中间件
import devMiddleware from '../server/middleware/webpack-dev-middleware'
// webpack配置文件
const webpackConfig = require('../scripts/webpack.config.js')

// https://webpack.docschina.org/api/compiler
const compiler = webpack(webpackConfig)

app.use(devMiddleware(compiler, {
  // 很重要,提供了静态资源的路径, 该路径与webpackConfig中的output.publicPath 对应
  publicPath: '/' 
}))

const PORT: number = Number(process.env.PORT) || 3000
app.listen(PORT) 
复制代码
  • 项目运行时通过 webpack-dev-middleware 中间件来调用webpack,以便 ctx.render 时渲染的就是 编译后 的模板文件;
  • 通过 glob 模块来遍历 src/entries/*.ts 下的入口文件,生成webpack的 entry 配置项 config.entry ;这也是 Webpack多页面配置 必不可少的一步;
  • 通过 ts-loader/babel-loader 等来编译入口文件以及入口文件中所引用的 ts/js 模块;
  • 通过 css-loader/sass-loader 等来编译入口文件中所引用的 scss/css 模块,并且直接通过 MiniCssExtractPlugin.loader 来独立生成css文件;
  • 通过 url-loader 等来编译引用的资源文件,如image;
  • 遍历 config.entry 来查找对应的模板文件,生成多页面的 HtmlWebpackPlugin 配置;

通过 webpack-dev-middleware 编译后的文件都在 内存中 , 但是 ejs 渲染所需要的模板文件都必须为真实的物理文件。因此需要有两个 output ,一个将静态资源放置在内存中,一个则直接编译后生成物理文件放置在 dist/views 中(方案见[ejs模板文件无法使用内存文件的解决方法]章节)。

实现Koa webpack-dev-middleware中间件

webpack-dev-middleware 是一个封装器(wrapper),它可以把 webpack 处理过的文件发送到一个 server。

webpack-dev-middleware是一个标准的express中间件,其一个重要作用就是将经过webpack编译打包的文件生成在内存中,以便下一个中间件使用。很多Cli使用的 webpack-dev-server 就是基于 express+webpack-dev-middleware 的实现。

由于webpack-dev-middleware是一个标准的express中间件,在Koa中不能直接使用它,因此需要将webpack-dev-middleware封装一下,以便Koa能够直接使用。

安装依赖

npm i -D webpack-dev-middleware @types/webpack-dev-middleware
复制代码

koa-webpack-dev-middleware

// path: server/middleware/webpack-dev-middleware.ts
// opts 配置同 webpack-dev-middleware

import * as WebpackDevMiddleware from 'webpack-dev-middleware'
import * as Koa from 'koa'
import { NextHandleFunction } from 'connect'
import webpack = require('webpack')

const devMiddleware = (compiler: webpack.ICompiler, opts: WebpackDevMiddleware.Options) => {
  const middleware = WebpackDevMiddleware(compiler, opts)
  return async (ctx: Koa.Context, next: NextHandleFunction) => {
    await middleware(ctx.req, {
      // @ts-ignore
      end: (content:string) => {
        ctx.body = content
      },
      setHeader: (name, value: any) => {
        ctx.set(name, value)
      }
    }, next)
  }
}

export default devMiddleware 
复制代码

glob遍历目录生成webpack入口

webpack 要实现一个多页面的配置,需要配置多个入口。随着深入的开发,入口往往是动态不定的,因此要实现一个动态获取入口的方法。

glob是一个允许正则匹配文件路径的模块,借助glob模块,很容易遍历某个目录下的所有文件来生成一个入口的map。

// path: scripts/webpack.config.js
// ...

// 获取入口文件
const entries = () => {
  // 通过 globa.sync 方法获取 src/entries/下的所有 .ts 文件
  const entriesFile = glob.sync(path.resolve(__dirname, '../src/entries/*.ts'))
  /**
   * 入口字典
   * {
   *    index: 'src/entries/index.ts',
   *    blog: 'src/entries/blog.ts',
   *    // ...
   * }
   */
  const map = Object.create(null)
  // 遍历匹配到的文件列表
  for (let i = 0; i < entriesFile.length; i++) {
    const filePath = entriesFile[i]
    // 提取文件名
    const match = filePath.match(/entries\/([a-zA-Z0-9-_]+)\.ts$/)
    // 将文件名作为 key, 存入map
    // 如: src/entries/index.ts , src/entries/blog.ts 将分别作为 index / blog 两个入口
    map[match[1]] = filePath
  }

  return map
}

// webpack config
const webpackConfig = {
  entry: entries(),
  // ...
}

module.exports = webpackConfig
复制代码

入口文件映射模板文件

由于前端源码使用的typescript/es6/scss,这些文件必须经过编译后才能被浏览器识别。同时,对资源文件的版本处理(加版本号),也需要借助 HtmlWebpackPlugin 这个插件注入到对应模板上。就像流程图中示意的那样,当访问路由时(如 localhost:3000/blog),ejs 加载的并不是 src/views 下的模板, 而是编译后(此时 css/js的引用已经注入到页面中)的位于 dist/views 下的新的模板文件 。多入口对应多个模板,每个模板文件和入口文件应该有个映射关系,这个关系可以通过维护一个map来实现(不利于增改),也可以通过文件命名规则来实现。这里采用命名规则来实现,这样更有利于自动化。

// path: scripts/webpack.config.js
// ...

// 遍历webpackConfig入口, key 对应了模板的文件名,这个命名规则可以更复杂些,比如增加对子目录的支持
// {
//   index: 'views/index.html',
//   blog: 'views/blog.html'
// }

const isProduction = process.env.NODE_ENV === 'production'

Object.keys(webpackConfig.entry).forEach(entry => {
  // 在 plugins 配置中增加了多个 HtmlWebpackPlugin 实例
  webpackConfig.plugins.push(new HtmlWebpackPlugin({
    filename: 'views/' + entry + '.html',
    template: path.resolve(__dirname, `../src/views/${entry}.html`),
    chunks: [entry],  // 将入口文件打包后的文件注入到对应的页面中
    alwaysWriteToDisk: true,  // 该配置项说明见 [ejs模板文件无法使用内存文件的解决方法] 章节
    minify: {
      removeComments: isProduction,
      collapseWhitespace: isProduction,
      removeAttributeQuotes: false,
      minifyCSS: isProduction,
      minifyJS: isProduction
    },
  }))
})

复制代码

ejs模板文件无法使用内存文件的解决方法

webpack-dev-middleware 的一个重要特性就是生成的文件都位于内存中,是一个内存型的文件系统。而 koa-ejs 作为渲染引擎只能加载真实的物理文件,当它加载 dist/vies/*.html 时会报文件未找到的错。因此,对模板文件的编译就不能再像其他资源一样生成于内存中,而是要把模板文件真真切切的生成为文件。 HtmlWebpackHarddiskPlugin 这个webpack插件可以完美解决。

npm i -D html-webpack-harddisk-plugin
复制代码
// path: scripts/webpack.config.js
// ...
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin')
// ... 见 [入口文件映射模板文件] 章节
webpackConfig.plugins.push(new HtmlWebpackPlugin({
  // 增加该配置项
  alwaysWriteToDisk: true, 
}))
// ...

// 应用 HtmlWebpackHarddiskPlugin 插件
webpackConfig.plugins.push(new HtmlWebpackHarddiskPlugin())
复制代码

前后端typescript配置文件的冲突

Server端和前端可能在typescript的配置上有所不同,尤其是在一些编译选项上。此时需要两个不同的配置文件。 tsconfig.json 是默认的TypeScript配置文件, 这里就作为Server端的配置项,根目录新建 tsconfig.front.json 作为前端的配置文件:

// ./tsconfig.front.json
{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true
  },
  "include":[
    "src/assets/**/*",
    "src/entries/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}
复制代码

同时,需要在webpack配置文件中指定配置文件路径:

// path: scripts/webpack.config.js
// ...
 webpackConfig.module = {
    rules: [{
        test: /\.tsx?$/,
        include: [
          path.resolve(__dirname, '../src/')
        ],
        use: [{
          loader: 'ts-loader',
          options: {
            // 指定配置文件
            configFile: '../tsconfig.front.json'
          }
        }],
      },
      // ...
    ],
  },
// ...
复制代码

至此,基于基于WEBPACK/TYPESCRIPT/KOA的前后端多页面开发环境配置完毕。配置 nodemon , nodemon将监视启动目录中的文件,如果有任何文件更改,nodemon将自动重新启动node应用程序。

运行 npm start , 实际上是运行 nodemon , nodemon将根据 nodemon.json 配置项来启动 npm run dev 命名。当src目录下的文件有任何变化时,它将重启应用程序。

// ./nodemon.json
{
  "watch": ["src", "server"],
  "exec": "npm run dev",
  "ext": "ts"
}
复制代码

package.jsonscripts 中加入运行脚本方便一键启动。

// ./package.json
{
  "scripts": {
    "start": "nodemon",
    "dev": "rm -rf dist && cross-env NODE_ENV=development ts-node bin/dev.server.ts",
  }
}
复制代码

生产环境(production)流程

基于Webpack/TypeScript/Koa的环境配置

相对而言,生产环境的配置就简单多了。当运行 npm run build 时,还是分两步走;

  • 通过 tsc 命令将 server 下的服务端代码全部编译到 dist/server 目录;
  • 通过 webpack 命令将 src 下的前端代码全部编译到 dist/* 相应目录;
  • 当通过 pm2 restart ./bin/pm2.json 或者 node ./bin/app.js (需要设置环境变量为 production ) 启动服务时,实际上已经运行的是编译后的代码。这里需要注意两点:
    • static 目录指向了 dist/static
    • views 目录指向了 dist/views
// ./server/app.ts
// 获取环境变量
const env = process.env.NODE_ENV || 'development'
const isDev = env === 'development'
require('koa-ejs')(app, {
  // root 为经过webpack编译后的真实模板路径
  // 生产环境下,server已经在dist目录,修改如下:
  root: path.resolve(__dirname, isDev ? '../dist/views' : '../views'),
})
复制代码
// ./bin/app.js
// 引用了编译后的 app.js 主文件
const app = require('../dist/server/app')
const path = require('path')
// 设置静态资源目录
app.use(require('koa-static')(path.resolve(__dirname, '../dist')))
复制代码

此时,dist目录结构如下:

.
├── server
│   ├── app.js
│   └── middleware
│       └── webpack-dev-middleware.js
├── static
│   ├── css
│   │   ├── blog.4dcddae.css
│   │   └── index.4dcddae.css
│   └── js
│       ├── blog.4dcddae.js
│       └── index.4dcddae.js
└── views
    ├── blog.html
    └── index.html
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Android和PHP开发最佳实践

Android和PHP开发最佳实践

黄隽实 / 机械工业出版社华章公司 / 2013-3-20 / 79.00元

本书是国内第一本同时讲述Android客户端开发和PHP服务端开发的经典著作。 本书以一个完整的微博应用项目实例为主线,由浅入深地讲解了Android客户端开发和PHP服务端开发的思路和技巧。从前期的产品设计、架构设计,到客户端和服务端的编码实现,再到性能测试和系统优化,以及最后的打包发布,完整地介绍了移动互联网应用开发的过程。同时,本书也介绍了Android系统中比较有特色的功能,比如Go......一起来看看 《Android和PHP开发最佳实践》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具