使用webpack搭建基于TypeScript的node開發環境

原文地址: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的基本概念 webpack 本質上是一個打包工具,它會根據代碼的內容解析模塊依賴,幫助我們把多個...
    cilla123閱讀 1,564評論 0 8
  • GitChat技術雜談 前言 本文較長,為了節省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,710評論 7 110
  • 大三的寒假已一月有余,三十多天幾乎是一直在虛度。時間慢慢流逝,原本制定了一堆計劃也無從實施。沒有目標、計劃...
    祎言閱讀 314評論 0 1
  • 那時候,時光變的很慢很慢!我靜靜地在塔頂看著那輪紅日,愈來愈低,直到低下了山頭,才不舍地離去……
    花晞閱讀 107評論 0 1
  • 時間 盡量少做“短半衰期”的事情長半衰期事件指南 積累可信的知識 訓練實踐技能 構建新的思維模式 提升審美品位 反...
    Lv1_Sans閱讀 363評論 0 0