正在学习node.js,这里介绍使用webpack来搭建基于typescript的node开发环境。
整个环境的必备功能 一套好的开发环境能让开发者专注于代码,而不必关系其它事情。这里先列出一些必要的条件。
一个命令就能启动项目。
一个命令能打包项目。
开发时代码改动能够自动更新,最好是热更新,而不是重启服务,这里为后面和前端代码一起调试做准备。
开发中能使用编辑器或者chrome调试,我本人习惯使用vscode。
基本搭建思路 全局使用ts,包括脚本,webpack配置文件。使用npm调用ts脚本,脚本使用ts-node执行,使用ts脚本调用webpack的api来打包编译文件。
npm scipts -> start-dev.ts -> webpack(webpackConfig)
这里解释下为什么使用ts脚本来调用webpack而不是直接将webpack命令写在npm scripts里。我的想法是All In Typescrpt
,尽量做到能用ts的就不用js,使用webpack的node api能轻松实现用ts写webpack配置。这样把做还有一个好处就是可以把webpack的配置写成动态的,根据传入参数来生成需要的配置。
选型 到这里项目的选型已经很明了了。
Typescript
项目使用的主语言,为前端开发添加强类型支持,能在编码过程中避免很多问题。
Koa
应用比较广泛。没有附加多余的功能,中间件即插即用。
Webpack
打包工具,开发中热加载。
ts-node
用来直接执行ts脚本。
start-server-webpack-plugin
很关键的webpack插件,能够在编译后直接启动服务,并且支持signal模式的热加载,配合webpack/hot/signal
很好用。
环境搭建 我们先用Koa写一个简单的web server,之后针对这个server来搭建环境。
项目代码 新建server/app.ts
,这个文件主要用来创建一个koa app。
1 2 3 4 5 6 7 8 9 import * as Koa from 'koa' ;const app = new Koa ();app.use (ctx => { ctx.body = 'Hello World' ; }); export default app;
我们需要另一个文件来启动server,并且监听server/app.ts
的改变,来热加载项目。
新建server/server.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import * as http from 'http' ;import app from './app' ;let currentApp = app.callback ();const server = http.createServer (currentApp);server.listen (3000 ); if (module .hot ) { module .hot .accept ('./app.ts' , () => { server.removeListener ('request' , currentApp); currentApp = app.callback (); server.on ('request' , currentApp); }); }
编译配置 在写webpack配置之前,我们先写下ts配置。
typescript配置 这里写的是webpack编译代码用的配置,后面还会介绍ts-node跑脚本时使用的配置。我们新建config/tsconfig.json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "compilerOptions" : { "module" : "es2015" , "noImplicitAny" : true , "sourceMap" : true , "moduleResolution" : "node" , "isolatedModules" : true , "target" : "es5" , "strictNullChecks" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "inlineSources" : false , "lib" : [ "es2015" ] } , "exclude" : [ "node_modules" , "**/*.spec.ts" ] }
webpack配置 一般情况下需要准备2套webpack配置,一套用来开发,一套用来发布。前面已经说过了使用webpack的api来打包为动态创建webpack配置提供了可能。所以这里我们写一个WebpackConfig
类,创建实例时根据参数,生成不同环境的配置。
开发环境和发布环境的区别 首先两个环境的mode是是不同的,开发环境是development
,发布环境是production
。关于mode的更多信息可查看webpack文档 。
开发环境需要热加载和启动服务,entry里需要配置’webpack/hot/signal’,使用webpack-node-externals
将’webpack/hot/signal’打包到代码里,添加HotModuleReplacementPlugin,使用start-server-webpack-plugin
启动服务和开启热加载。
webpack配置内容 现在我们来写下webpack配置。重点写在注释中了。
新建文件config/Webpack.config.ts
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import * as path from 'path' ;import * as StartServerPlugin from "start-server-webpack-plugin" ;import * as webpack from 'webpack' ;import * as nodeExternals from 'webpack-node-externals' ;import {Configuration , ExternalsElement } from 'webpack' ;class WebpackConfig implements Configuration { target : Configuration ['target' ] = "node" ; mode : Configuration ['mode' ] = 'production' ; entry = [path.resolve (__dirname, '../server/server.ts' )]; output = { path : path.resolve (__dirname, '../dist' ), filename : "server.js" }; externals : ExternalsElement [] = []; module = { rules : [ { test : /\.tsx?$/ , use : [ { loader : 'ts-loader' , options : { transpileOnly : true , configFile : path.resolve (__dirname, './tsconfig.json' ) } } ], exclude : /node_modules/ } ] }; resolve = { extensions : [".ts" , ".js" , ".json" ], }; plugins = [new webpack.NoEmitOnErrorsPlugin ()]; constructor (mode: Configuration['mode' ] ) { this .mode = mode; if (mode === 'development' ) { this .entry .push ('webpack/hot/signal' ); this .externals .push ( nodeExternals ({ whitelist : ['webpack/hot/signal' ] }) ); const devPlugins = [ new webpack.HotModuleReplacementPlugin (), new StartServerPlugin ({ name : 'server.js' , signal : true , nodeArgs : ['--inspect' ] }), ] this .plugins .push (...devPlugins); } } } export default WebpackConfig ;
这里需要注意,Windows 系统不支持 webpack/hot/signal
。需要使用 webpack/hot/poll
。
具体配置区别参见 commit
Windows 下的配置: webpack.config.ts
编译脚本 使用ts-node来启动脚本时需要使用新tsconfig.json
,这个编译目标是在node中运行。
在项目根目录新建tsconfig.json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "compilerOptions" : { "module" : "commonjs" , "noImplicitAny" : true , "sourceMap" : true , "moduleResolution" : "node" , "isolatedModules" : true , "target" : "es5" , "strictNullChecks" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "inlineSources" : false , "lib" : [ "es2015" ] } , "exclude" : [ "node_modules" , "**/*.spec.ts" ] }
开发脚本 启动开发脚本,scripts/start-dev.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 import * as webpack from 'webpack' ;import WebpackConfig from '../config/Webpack.config' ;const devConfig = new WebpackConfig ('development' );webpack (devConfig).watch ({ aggregateTimeout : 300 }, (err: Error ) => { console .log (err); });
在package.json
中添加
1 2 3 "scripts" : { "dev" : "rm -rf ./dist && ts-node ./scripts/start-dev.ts" } ,
执行yarn dev
,我们能看到项目启动了: 命令行输出: 浏览器展示:
修改server/app.ts
1 2 3 4 5 6 7 8 9 10 11 12 import * as Koa from 'koa'; const app = new Koa(); app.use(ctx => { - ctx.body = 'Hello World'; + ctx.body = 'Hello Marx'; }); export default app;
能看到命令行输出:
刷新浏览器:
可以看到热更新已经生效了。
发布打包脚本 新建打包脚本scripts/build.ts
:
1 2 3 4 5 6 7 8 9 10 import * as webpack from 'webpack' ;import WebpackConfig from '../config/Webpack.config' ;const buildConfig = new WebpackConfig ('production' );webpack (buildConfig).run ((err: Error ) => { console .log (err); });
在package.json
添加build
命令:
1 2 3 4 "scripts": { + "build": "rm -rf ./dist && ts-node ./scripts/build.ts", "dev": "rm -rf ./dist && ts-node ./scripts/start-dev.ts" },
执行yarn build
就能看到dist/server.js
。这个就是我们项目的产出。其中包含了node_modules
中的依赖,这样做是否合理,还在探索中,欢迎讨论。
到此整个环境搭建过程就完成了。
完整项目代码MarxJiao/webpack-node
总结 这个项目重点在于热加载和All In Typescript。
1. 为什么后端代码要热加载? 为了方便使用webpack中间件打包前端代码,这样不用重启后端服务就不用重新编译前端代码,重新编译是很耗时的。后续使用时,流程大概是这样的
start-dev.ts -> server端的webpack -> server代码 -> webpack中间件 -> 前端代码
这样能保证开发时只需要一个入口来启动,前后端都能热加载。
2. 实现热加载的关键点
webpack配置mode: 'development'
,为了NamedModulesPlugin
插件
webpack配置entry: ‘webpack/hot/signal’ 或 ‘webpack/hot/poll?1000’
将’webpack/hot/signal’打包进代码:nodeExternals({whitelist: [‘webpack/hot/signal’]}) 或 ‘webpack/hot/poll?1000’
使用HotModuleReplacementPlugin
start-server-webpack-plugin配置signal: true
,’webpack/hot/poll?1000’ 时配置为 false
tsconfig.json配置"module": "es2015"
使用单独的文件来启动server,监听热加载的文件,server/server.ts
3. tsconfig ts-node运行脚本的tsconfig和ts-loader打包代码时的tsconfig不同。
ts-node用的config直接将代码用tsc编译后在node运行,在node 8.x以下的版本中不能使用import,所以module要用commonjs
。
webpack打包的代码要热加载,需要用es module,这里我们使用es2015
。
参考资料