前言
ok,昨天那篇文章看完有什麼感覺呢?今天的文章又是比較高大上的,來自美信FED前端團隊的@luoye童鞋投稿的。前端
正文從這開始~node
webpack 在前端領域的模塊化和代碼構建方面有着無比強大的功能,經過一些特殊的配置甚至能夠實現前端代碼的實時構建、ES6/7新特性支持以及熱重載,這些功能一樣能夠運用於後臺 nodejs 的應用,讓後臺的開發更加順暢,服務更加靈活,怎麼來呢?往下看。webpack
先梳理下咱們將要解決的問題:web
node端代碼構建npm
ES6/7 新特性支持json
node服務代碼熱重載瀏覽器
node端的代碼實際上是不用編譯或者構建的,整個node的環境有它本身的一個模塊化或者依賴機制,可是即便是如今最新的node版本,對ES6/7的支持仍是捉襟見肘。固然使用一些第三方庫能夠作到支持相似async/await
這樣的語法,可是畢竟不是規範不是標準,這樣看來,node端的代碼仍是有構建的須要的。這裏咱們選取的工具就是 webpack
以及它的一些 loader
。babel
首先,一個 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:其它的還沒發現,估摸很多,遇到了谷歌吧…
構建 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
,藉此來編譯代碼。
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
一遍,而後代碼便是最新的代碼。
固然,若是你在某個須要熱重載的模塊中又依賴另外一個模塊,或者說動態的依賴了另外一個模塊,這樣的模塊並不會熱重載。
動態 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 了,配置文件也必須跟上。
安裝好 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'] } }
大體流程就是如此,坑確定還有,遇到的話手動谷歌吧~