【第562期】用 webpack 構建 node 後端代碼,使其支持 js 新特性並實現熱重載

前言
ok,昨天那篇文章看完有什麼感覺呢?今天的文章又是比較高大上的,來自美信FED前端團隊的@luoye童鞋投稿的。前端

正文從這開始~node

webpack 在前端領域的模塊化和代碼構建方面有着無比強大的功能,經過一些特殊的配置甚至能夠實現前端代碼的實時構建、ES6/7新特性支持以及熱重載,這些功能一樣能夠運用於後臺 nodejs 的應用,讓後臺的開發更加順暢,服務更加靈活,怎麼來呢?往下看。webpack

先梳理下咱們將要解決的問題:web

  • node端代碼構建npm

  • ES6/7 新特性支持json

  • node服務代碼熱重載瀏覽器

node端代碼構建

node端的代碼實際上是不用編譯或者構建的,整個node的環境有它本身的一個模塊化或者依賴機制,可是即便是如今最新的node版本,對ES6/7的支持仍是捉襟見肘。固然使用一些第三方庫能夠作到支持相似async/await 這樣的語法,可是畢竟不是規範不是標準,這樣看來,node端的代碼仍是有構建的須要的。這裏咱們選取的工具就是 webpack 以及它的一些 loaderbabel

首先,一個 node app 一定有一個入口文件 app.js ,按照 webpack 的規則,咱們能夠把全部的代碼打包成一個文件 bundle.js ,而後運行這個 bundle.js 便可,webpack.config.js以下:app

var webpcak = require('webpack');module.exports = {
    entry: [        './app.js'
    ],
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    }
}

可是有一個很嚴重的問題,這樣打包的話,一些 npm 中的模塊也會被打包進這個 bundle.js,還有 node 的一些原生模塊,好比 fs/path 也會被打包進來,這明顯不是咱們想要的。因此咱們得告訴 webpack,你打包的是 node 的代碼,原生模塊就不要打包了,還有 node_modules 目錄下的模塊也不要打包了,webpack.config.js 以下:async

var webpcak = require('webpack');var nodeModules = {};
fs.readdirSync('node_modules')
    .filter(function(x) {        return ['.bin'].indexOf(x) === -1;
    })
    .forEach(function(mod) {
        nodeModules[mod] = 'commonjs ' + mod;
    });module.exports = {
    entry: [        './app.js'
    ],
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    },
    target: 'node',
    externals: nodeModules
}

主要就是在 webpack 的配置中加上 target: 'node' 告訴 webpack打包的對象是 node 端的代碼,這樣一些原生模塊 webpack 就不會作處理。另外一個就是 webpack 的 externals 屬性,這個屬性的主要做用就是告知 webpack 在打包過程當中,遇到 externals 中聲明的模塊不用處理。

好比在前端中, jQuery 的包經過 CDN 的方式以 script 標籤引入,若是此時在代碼中出現 require('jQuery') ,而且直接用 webpack 打包比定會報錯。由於在本地並無這樣的一個模塊,此時就必須在 externals 中聲明 jQuery 的存在。也就是 externals中的模塊,雖然沒有被打包,可是是代碼運行是所要依賴的,而這些依賴是直接存在在整個代碼運行環境中,並不用作特殊處理。

在 node 端所要作的處理就是過濾出 node_modules 中全部模塊,而且放到 externals中。

這個時候咱們的代碼應該能夠構建成功了,而且是咱們指望的形態,可是不出意外的話,你仍是跑不起來,由於有不小的坑存在,繼續往下看。

  • 坑1:__durname __filename 指向問題

    打包以後的代碼你會發現 __durname __filename 所有都是 / ,這兩個變量在 webpack 中作了一些自定義處理,若是想要正確使用,在配置中加上

    context: __dirname,
    node: {
      __filename: false,
      __dirname: false},
  • 坑2:動態 require 的上下文問題

    這一塊比較大,放到後面講,跟具體代碼有關,和配置無關

  • 坑n:其它的還沒發現,估摸很多,遇到了谷歌吧…

ES6/7 新特性支持

構建 node 端代碼的目標之一就是使用ES6/7中的新特性,要實現這樣的目標 babel 是咱們的不二選擇。

首先,先安裝 babel 的各類包 npm install babel-core babel-loader babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-0 --save-dev json-loader -d  

而後修改 webpack.config.js ,以下:

var webpcak = require('webpack');var nodeModules = {};
fs.readdirSync('node_modules')
    .filter(function(x) {        return ['.bin'].indexOf(x) === -1;
    })
    .forEach(function(mod) {
        nodeModules[mod] = 'commonjs ' + mod;
    });module.exports = {
    entry: [        './app.js'
    ],
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    },
    target: 'node',
    externals: nodeModules,
    context: __dirname,
    node: {
        __filename: false,
        __dirname: false
    },    module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: [
                path.resolve(__dirname, "node_modules"),
            ],
            query: {
                plugins: ['transform-runtime'],
                presets: ['es2015', 'stage-0'],
            }
        }, {
            test: /\.json$/,
            loader: 'json-loader'
        }]
    },
    resolve: {
        extensions: ['', '.js', '.json']
    }
}

主要就是配置 webpack 中的 loader ,藉此來編譯代碼。

node服務代碼熱重載

webpack 極其牛叉的地方之一,開發的時候,實時的構建代碼,而且,實時的更新你已經加載的代碼,也就是說,不用手動去刷新瀏覽器,便可以獲取最新的代碼並執行。

這一點一樣能夠運用在 node 端,實現即時修改即時生效,而不是 pm2 那種重啓的方式。

首先,修改配置文件,以下:

entry: [    'webpack/hot/poll?1000',    './app.js'],// ...plugins: [    new webpack.HotModuleReplacementPlugin()
]

這個時候,若是執行 webpack --watch & node app.js ,你的代碼修改以後就能夠熱重載而不用重啓應用,固然,代碼中也要作相應改動,以下:

var hotModule = require('./hotModule');// do something else// 若是想要 hotModule 模塊熱重載if (module.hot) {    module.hot.accept('./hotModule.js', function() {        var newHotModule = require('./hotModule.js');        // do something else
    });
}

思路就是,若是須要某模塊熱重載,就把它包一層,若是修改了,webpack 從新打包了,從新 require 一遍,而後代碼便是最新的代碼。

固然,若是你在某個須要熱重載的模塊中又依賴另外一個模塊,或者說動態的依賴了另外一個模塊,這樣的模塊並不會熱重載。

webpack 動態 require

動態 require 的場景包括:

  • 場景一:在代碼運行過程當中遍歷某個目錄,動態 reauire,好比

      //app.js
      var rd = require('rd');  // 遍歷路由文件夾,自動掛載路由
      var routers = rd.readFileFilterSync('./routers', /\.js/);
      routers.forEach(function(item) {      require(item);
      })

    這個時候你會發現 './routers' 下的require都不是本身想要的,而後在 bundle.js 中找到打包以後的相應模塊後,你能夠看到,動態 require 的對象都是 app.js 同級目錄下的 js 文件,而不是 './routers' 文件下的 js 文件。爲何呢?

    webpack 在打包的時候,必須把你可能依賴的文件都打包進來,而且編上號,而後在運行的時候 require 相應的模塊 ID 便可,這個時候 webpack 獲取的動態模塊,就再也不是你指定的目錄'./routers' 了,而是相對於當前文件的目錄,因此,必須修正 require 的上下文,修改以下:

      // 獲取正確的模塊
      var req = require.context("./routers", true, /\.js$/);  var routers = rd.readFileFilterSync('./routers', /\.js/);
      routers.forEach(function(item) {      // 使用包涵正確模塊的已經被修改過的 `require` 去獲取模塊
          req(item);
      })
  • 場景二:在 require 的模塊中含有變量,好比

      var myModule = require(isMe ? './a.js' : './b.js');  // 或者
      var testMoule = require('./mods' + name + '.js');

    第一種的處理方式在 webpack 中的處理是把模塊 ./a.js ./b.js 都包涵進來,根據變量不一樣 require 不一樣的模塊。

    第二種的處理方式和場景一相似,獲取 ./mods/ 目錄下的全部模塊,而後重寫了 require ,而後根據變量不一樣加載不通的模塊,因此本身處理的時候方法相似。

用 ES6/7 寫 webpack.config.js

項目都用 ES6/7 了,配置文件也必須跟上。

安裝好 babel 編譯所須要的幾個依賴包,而後把 webpack.config.js 改成 webpack.config.babel.js ,而後新建 .babelrc 的 babel 配置文件,加入

{
  "presets": ["es2015"]}

而後和往常同樣執行 webpack 的相關命令便可。

完整 webpack.config.babel.js 以下:

import webpack from 'webpack';
import fs from 'fs';
import path from 'path';let nodeModules = {};
fs.readdirSync('node_modules')
    .filter((x) => {        return ['.bin'].indexOf(x) === -1;
    })
    .forEach((mod) => {
        nodeModules[mod] = 'commonjs ' + mod;
    });

export default {
    cache: true,
    entry: [        'webpack/hot/poll?1000',        './app.js'
    ],
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    },
    context: __dirname,
    node: {
        __filename: false,
        __dirname: false
    },
    target: 'node',
    externals: nodeModules,    module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: [
                path.resolve(__dirname, "node_modules"),
            ],
            query: {
                plugins: ['transform-runtime'],
                presets: ['es2015', 'stage-0'],
            }
        }, {
            test: /\.json$/,
            loader: 'json-loader'
        }]
    },
    plugins: [        new webpack.HotModuleReplacementPlugin()
    ],
    resolve: {
        extensions: ['', '.js', '.json']
    }
}

大體流程就是如此,坑確定還有,遇到的話手動谷歌吧~

相關文章
相關標籤/搜索