美文网首页前端让前端飞程序员
使用webpack搭建基于TypeScript的node开发环境

使用webpack搭建基于TypeScript的node开发环境

作者: MarxJiao | 来源:发表于2018-04-22 13:57 被阅读4410次

    原文地址:https://marxjiao.com/2018/04/10/node-webpack/

    正在学习node.js,这里介绍使用webpack来搭建基于TypeScript的node开发环境。

    整个环境的必备功能

    一套好的开发环境能让开发者专注于代码,而不必关系其它事情。这里先列出一些必要的条件。

    1. 一个命令就能启动项目。
    2. 一个命令能打包项目。
    3. 开发时代码改动能够自动更新,最好是热更新,而不是重启服务,这里为后面和前端代码一起调试做准备。
    4. 开发中能使用编辑器或者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。

    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

    import * as http from 'http';
    import app from './app';
    
    // app.callback() 会返回一个能够通过http.createServer创建server的函数,类似express和connect。
    let currentApp = app.callback();
    // 创建server
    const server = http.createServer(currentApp);
    server.listen(3000);
    
    // 热加载
    if (module.hot) {
        // 监听./app.ts
        module.hot.accept('./app.ts', () => {
            // 如果有改动,就使用新的app来处理请求
            server.removeListener('request', currentApp);
            currentApp = app.callback();
            server.on('request', currentApp);
        });
    }
    

    编译配置

    在写webpack配置之前,我们先写下ts配置和babel配置。

    TypeScript配置

    这里写的是webpack编译代码用的配置,后面还会介绍ts-node跑脚本时使用的配置。我们新建config/tsconfig.json

    {
        "compilerOptions": {
            // module配置很重要,千万不能配置成commonjs,热加载会失效
            "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"
        ]
    }
    

    babel配置

    .babelrc,"modules": false很重要,tree shakingHMR都靠它。

    {
      "presets": [["env", {"modules": false}]]
    }
    

    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

    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 {
        // node环境
        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[] = [];
        // loader们
        module = {
            rules: [
                {
                    test: /\.tsx?$/,
                    use: [
                        // tsc编译后,再用babel处理
                        {loader: 'babel-loader',},
                        {
                            loader: 'ts-loader',
                            options: {
                                // 加快编译速度
                                transpileOnly: true,
                                // 指定特定的ts编译配置,为了区分脚本的ts配置
                                configFile: path.resolve(__dirname, './tsconfig.json')
                            }
                        }
                    ],
                    exclude: /node_modules/
                },
                {
                    test: /\.jsx?$/,
                    use: 'babel-loader',
                    exclude: /node_modules/
                }
            ]
        };
        resolve = {
            extensions: [".ts", ".js", ".json"],
        };
        // 开发环境也使用NoEmitOnErrorsPlugin
        plugins = [new webpack.NoEmitOnErrorsPlugin()];
        constructor(mode: Configuration['mode']) {
            // 配置mode,production情况下用上边的默认配置就ok了。
            this.mode = mode;
            if (mode === 'development') {
                // 添加webpack/hot/signal,用来热更新
                this.entry.push('webpack/hot/signal');
                this.externals.push(
                    // 添加webpack/hot/signal,用来热更新
                    nodeExternals({
                        whitelist: ['webpack/hot/signal']
                    })
                );
                const devPlugins = [
                    // 用来热更新
                    new webpack.HotModuleReplacementPlugin(),
                    // 启动服务
                    new StartServerPlugin({
                        // 启动的文件
                        name: 'server.js',
                        // 开启signal模式的热加载
                        signal: true,
                        // 为调试留接口
                        nodeArgs: ['--inspect']
                    }),
                ]
                this.plugins.push(...devPlugins);
            }
        }
    }
    
    export default WebpackConfig;
    

    编译脚本

    使用ts-node来启动脚本时需要使用新tsconfig.json,这个编译目标是在node中运行。

    在项目根目录新建tsconfig.json:

    {
        "compilerOptions": {
            // 为了node环境能直接运行
            "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:

    import * as webpack from 'webpack';
    
    import WebpackConfig from '../config/Webpack.config';
    
    // 创建编译时配置
    const devConfig = new WebpackConfig('development');
    // 通过watch来实时编译
    webpack(devConfig).watch({
        aggregateTimeout: 300
    }, (err: Error) => {
        console.log(err);
    });
    
    

    package.json中添加

    "scripts": {
        "dev": "rm -rf ./dist && ts-node ./scripts/start-dev.ts"
    },
    

    执行yarn dev,我们能看到项目启动了:
    命令行输出:

    命令行输出

    浏览器展示:


    浏览器展示

    修改server/app.ts

    
    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

    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命令:

    "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/signal'打包进代码:nodeExternals({whitelist: ['webpack/hot/signal']})
    • 使用HotModuleReplacementPlugin
    • start-server-webpack-plugin配置signal: true
    • babel配置"modules": 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

    参考资料

    相关文章

      网友评论

        本文标题:使用webpack搭建基于TypeScript的node开发环境

        本文链接:https://www.haomeiwen.com/subject/prtclftx.html