React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 4

內(nèi)容回顧

前面的篇幅主要介紹了:

到目前階段图毕,你對Express, Webpack, React已經(jīng)有了基本的了解,但前端頁面服務(wù)和API服務(wù)目前還是分離的,本篇文章主要介紹:

  • 使用Webpack打包工程
  • 使用常用的插件

package.json

修改package.json文件潭千,安裝需要的package

{
  "name": "react_express_blog",
  "version": "1.0.0",
  "description": "react-express-mongo demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch-client": "cross-env NODE_ENV=development node ./server/index.js",
    "start-prod": "cross-env NODE_ENV=production node bin/server",
    "start-dev-api": "nodemon --watch server/api server/api/index.js",
    "start": "npm run watch-client & npm run start-dev-api"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/sam408130/react-blog/tree/part3"
  },
  "author": "Sam",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/sam408130/react-blog/tree/part3/issues"
  },
  "homepage": "https://github.com/sam408130/react-blog/tree/part3",
  "dependencies": {
    "antd": "^2.13.1",
    "axios": "^0.16.2",
    "bluebird": "^3.5.0",
    "body-parser": "^1.18.0",
    "compression": "^1.7.0",
    "connect-history-api-fallback": "^1.3.0",
    "cookie-parser": "^1.4.3",
    "cookies": "^0.7.1",
    "dateformat": "^3.0.2",
    "echarts-for-react": "^2.0.0",
    "express": "^4.15.4",
    "express-session": "^1.15.5",
    "http-proxy": "^1.16.2",
    "markdown": "^0.5.0",
    "mongoose": "^4.11.11",
    "qs": "^6.5.1",
    "react": "^15.6.1",
    "react-addons-pure-render-mixin": "^15.6.0",
    "react-dom": "^15.6.1",
    "react-helmet": "^5.2.0",
    "react-markdown": "^2.5.0",
    "react-redux": "^5.0.6",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "react-slick": "^0.15.4",
    "redux": "^3.7.2",
    "redux-saga": "^0.15.6",
    "remark": "^8.0.0",
    "remark-react": "^4.0.1",
    "serialize-javascript": "^1.4.0",
    "serve-favicon": "^2.4.4"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.4",
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-import": "^1.4.0",
    "babel-plugin-react-transform": "^2.0.2",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-remove-console": "^6.8.5",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.6.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-react-hmre": "^1.1.1",
    "babel-preset-react-optimize": "^1.0.1",
    "babel-preset-stage-0": "^6.24.1",
    "babel-register": "^6.26.0",
    "babel-runtime": "^6.26.0",
    "clean-webpack-plugin": "^0.1.16",
    "concurrently": "^3.5.0",
    "cross-env": "^5.0.5",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^0.11.2",
    "html-webpack-plugin": "^2.30.1",
    "install": "^0.10.1",
    "less": "^2.7.2",
    "less-loader": "^4.0.5",
    "node-loader": "^0.6.0",
    "node-sass": "^4.5.3",
    "nodemon": "^1.12.0",
    "npm": "^5.4.1",
    "open-browser-webpack-plugin": "0.0.5",
    "postcss-loader": "^2.0.6",
    "progress-bar-webpack-plugin": "^1.10.0",
    "react-hot-loader": "^3.0.0-beta.6",
    "redbox-react": "^1.5.0",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "url-loader": "^0.5.9",
    "webpack": "^3.5.6",
    "webpack-dev-middleware": "^1.12.0",
    "webpack-hot-middleware": "^2.19.1",
    "webpack-isomorphic-tools": "^3.0.3"
  }
}

上面的配置文件是工程所需要的所有模塊突委,先安裝,下面會逐一介紹用途:

npm install 

webpack配置文件

在根目錄創(chuàng)建webpack開發(fā)環(huán)境的配置文件webpack.dev.js

首先看一下完整的文件內(nèi)容

const pathLib = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CleanPlugin = require('clean-webpack-plugin');
const config = require('./config/config');

const ROOT_PATH = pathLib.resolve(__dirname);
const ENTRY_PATH = pathLib.resolve(ROOT_PATH, 'src');
const OUTPUT_PATH = pathLib.resolve(ROOT_PATH, 'build');
const AppHtml = pathLib.resolve(ENTRY_PATH,'index.html')
console.log(pathLib.resolve(ENTRY_PATH, 'index.js'));

module.exports = {
    entry: {
        index: [
          'react-hot-loader/patch',
          `webpack-hot-middleware/client?path=http://${config.host}:${config.port}/__webpack_hmr`,
          'babel-polyfill',
          pathLib.resolve(ENTRY_PATH, 'index.js')
        ],
        vendor: ['react', 'react-dom', 'react-router-dom']
    },
    output: {
        path: OUTPUT_PATH,
        publicPath: '/',
        filename: '[name]-[hash:8].js'
    },
    devtool: 'cheap-module-eval-source-map',
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ['style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true,
                            localIdentName: '[name]-[local]-[hash:base64:5]',
                            importLoaders: 1
                        }
                    },
                    'postcss-loader'
                ]
            },
            {
                test: /\.css$/,
                include: /node_modules/,
                use: ['style-loader',
                    {
                        loader: 'css-loader'
                    },
                    'postcss-loader'
                ]
            },
            {
                test: /\.less$/,
                use: ["style-loader", 'css-loader', "postcss-loader", "less-loader"]
            },
            {
                test: /\.(png|jpg|gif|JPG|GIF|PNG|BMP|bmp|JPEG|jpeg)$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192
                        }
                    }
                ]
            },
            {
                test: /\.(eot|woff|ttf|woff2|svg)$/,
                use: 'url-loader'
            }
        ]
    },
    plugins: [
        new CleanPlugin(['build']),
        new ProgressBarPlugin(),
        new webpack.optimize.AggressiveMergingPlugin(),//改善chunk傳輸
        new webpack.HotModuleReplacementPlugin(),
        new webpack.DefinePlugin({
            "progress.env.NODE_ENV": JSON.stringify('development')
        }),
        new HtmlWebpackPlugin({
            inject: true,
            template: AppHtml,
        }),
        new webpack.NoEmitOnErrorsPlugin(),//保證出錯時頁面不阻塞瓦侮,且會在編譯結(jié)束后報錯
        new webpack.HashedModuleIdsPlugin(),//用 HashedModuleIdsPlugin 可以輕松地實現(xiàn) chunkhash 的穩(wěn)定化
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: function (module) {
                return module.context && module.context.indexOf('node_modules') !== -1;
            }
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: "manifest"
        }),
        new OpenBrowserPlugin({
            url: `http://${config.host}:${config.port}`
        })
    ],
    resolve: {
        extensions: ['.js', '.json', '.sass', '.scss', '.less', 'jsx']
    }
}

文件中的幾個參數(shù)涵卵,entry, output, devtool, module, plugins起的作用莱褒,在上一篇內(nèi)容都有介紹击困,沒有完全理解的同學(xué),可以再回看一下React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 3 Express + Mongodb創(chuàng)建Server端webpack的介紹广凸。

定義參數(shù)

文件頭部是下面配置內(nèi)容中使用的參數(shù)阅茶,統(tǒng)一寫在頭部可以方便調(diào)用

entry

index部分定義工程的入口,文件中分為4個部分:

  • react-hot-loader/patch 結(jié)合babel-polyfill谅海, 允許我們在使用jsx語法編寫react時脸哀,能讓修改的部分自動刷新。但這和自動刷新網(wǎng)頁是不同的扭吁,因為 hot-loader 并不會刷新網(wǎng)頁撞蜂,而僅僅是替換你修改的部分。
  • webpack-hot-middleware侥袜,允許Express 結(jié)合 Webpack 實現(xiàn)HMR蝌诡。HMR 即 Hot Module Replacement 是 Webpack 一個重要的功能。它可以使我們不用通過手動地刷新瀏覽器頁面實現(xiàn)將我們的更新代碼實時應(yīng)用到當(dāng)前頁面中枫吧。
  • index.js是博客的入口頁面
  • vendor浦旱,該部分的作用是,將指定的模塊打包成一個vendor.js文件九杂,從最后編譯的bundle.js工程文件中分離出來颁湖。
output

該部分定義打包文件輸出的路徑宣蠕,由于我們使用了html-webpack-plugin插件,這里就可以使用文件名加hash的方法甥捺,解決緩存問題抢蚀。

devtool

開發(fā)總是離不開調(diào)試,方便的調(diào)試能極大的提高開發(fā)效率镰禾,不過有時候通過打包后的文件思币,你是不容易找到出錯了的地方,對應(yīng)的你寫的代碼的位置的羡微,Source Maps就是來幫我們解決這個問題的谷饿。

通過簡單的配置,webpack就可以在打包時為我們生成的source maps妈倔,這為我們提供了一種對應(yīng)編譯文件和源文件的方法博投,使得編譯后的代碼可讀性更高,也更容易調(diào)試盯蝴。

在webpack的配置文件中配置source maps毅哗,需要配置devtool,它有以下四種不同的配置選項捧挺,各具優(yōu)缺點虑绵,描述如下:

devtool 配置結(jié)果
source-map 在一個單獨的文件中產(chǎn)生一個完整且功能完全的文件。這個文件具有最好的source map闽烙,但是它會減慢打包速度翅睛;
cheap-module-source-map 在一個單獨的文件中生成一個不帶列映射的map,不帶列映射提高了打包速度黑竞,但是也使得瀏覽器開發(fā)者工具只能對應(yīng)到具體的行捕发,不能對應(yīng)到具體的列(符號),會對調(diào)試造成不便很魂;
eval-source-map 使用eval打包源文件模塊扎酷,在同一個文件中生成干凈的完整的source map。這個選項可以在不影響構(gòu)建速度的前提下生成完整的sourcemap遏匆,但是對打包后輸出的JS文件的執(zhí)行具有性能和安全的隱患法挨。在開發(fā)階段這是一個非常好的選項,在生產(chǎn)階段則一定不要啟用這個選項
cheap-module-eval-source-map 這是在打包文件時最快的生成source map的方法幅聘,生成的Source Map 會和打包后的JavaScript文件同行顯示凡纳,沒有列映射,和eval-source-map選項具有相似的缺點喊暖;

這里我們使用cheap-module-eval-source-map

module

上一節(jié)我們介紹了loaders的作用惫企,以及如何讓css文件模塊化。module部分是我們使用的所有l(wèi)oaders,在完成該部分時狞尔,記得在根路徑上配置.babelrcpostcss.config.js文件

// .babelrc
{
  "presets": ["es2015","react","stage-0","env"],
  "plugins": ["react-hot-loader/babel",["import", { "libraryName": "antd", "style": true }],"transform-runtime","transform-class-properties"],
  "env": {
    "production":{
      "preset":["react-optimize"]
    }
  }
}
// postcss.config.js
module.export = {
    plugins:[
        require('autoprefixer')({browsers:'last 2 versions'})
    ]
};
plugins
1. html-webpack-plugin

這個插件的主要作用有兩個:

  • 為html文件中引入的外部資源如script丛版、link動態(tài)添加每次compile后的hash,防止引用緩存的外部文件問題
  • 可以生成創(chuàng)建html入口文件偏序,比如單頁面可以生成一個html文件入口页畦,配置N個html-webpack-plugin可以生成N個頁面入口

代碼中是這么定義的

const AppHtml = pathLib.resolve(ENTRY_PATH,'index.html')
...
new HtmlWebpackPlugin({
     inject: true,
     template: AppHtml,
}),

AppHtml是我們定義的模板文件:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <title>Sam's Blog</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

這樣配置后,打包時這個html文件會自動引用打包好的bundle.js和其他文件

2. open-browser-webpack-plugin

這個插件允許傳入一個url地址研儒,作用是當(dāng)webpack打包成功后豫缨,會在瀏覽器中自動打開傳入的地址,使用方法是:

        new OpenBrowserPlugin({
            url: `http://${config.host}:${config.port}`
        })
3. progress-bar-webpack-plugin

打包過程顯示進(jìn)度

屏幕快照 2017-11-02 下午6.19.18.png
4. clean-webpack-plugin

用戶清除生產(chǎn)環(huán)境下端朵,無效的輸出文件好芭。

5. webpack.optimize.AggressiveMergingPlugin

在工程文件中,經(jīng)常會在不同文件中引用同一個module冲呢,使用該配置可以防止重復(fù)打包舍败,減小最終文件的大小。

啟動項目

我們的項目入口是服務(wù)端的server.js

import path from 'path';
import Express from 'express';
import favicon from 'serve-favicon'
import httpProxy from 'http-proxy';
import compression from 'compression';
import connectHistoryApiFallback from 'connect-history-api-fallback';
import config from '../config/config';

const app = new Express();
const port = config.port;
const targetUrl = `http://${config.apiHost}:${config.apiPort}`;
const proxy = httpProxy.createProxyServer({
    target:targetUrl
});

app.use('/api', (req,res) => {
    proxy.web(req,res,{target:targetUrl})
});

app.use('/', connectHistoryApiFallback());
app.use('/',Express.static(path.join(__dirname,"..",'build')));

app.use(compression());
app.use(favicon(path.join(__dirname,'..','public','favicon.ico')));


//熱更新
if(process.env.NODE_EVN!=='production'){
    const Webpack = require('webpack');
    const WebpackDevMiddleware = require('webpack-dev-middleware');
    const WebpackHotMiddleware = require('webpack-hot-middleware');
    const webpackConfig = require('../webpack.dev');

    const compiler = Webpack(webpackConfig);

    app.use(WebpackDevMiddleware(compiler, {
        publicPath: '/',
        stats: {colors: true},
        lazy: false,
        watchOptions: {
            aggregateTimeout: 300,
            poll: true
        },
    }));
    app.use(WebpackHotMiddleware(compiler));
}

app.listen(port,(err)=>{
    if(err){
        console.error(err)
    }else{
        console.log(`===>open http://${config.host}:${config.port} in a browser to view the app`);
    }
});

在生產(chǎn)環(huán)境下敬拓,前段部分頁面的路由通過打包的文件加載

app.use('/',Express.static(path.join(__dirname,"..",'build')));

開發(fā)環(huán)境下邻薯,通過webpack-dev-middlewarewebpack-hot-middleware實現(xiàn)頁面加載和熱更新

if(process.env.NODE_EVN!=='production'){
    const Webpack = require('webpack');
    const WebpackDevMiddleware = require('webpack-dev-middleware');
    const WebpackHotMiddleware = require('webpack-hot-middleware');
    const webpackConfig = require('../webpack.dev');

    const compiler = Webpack(webpackConfig);

    app.use(WebpackDevMiddleware(compiler, {
        publicPath: '/',
        stats: {colors: true},
        lazy: false,
        watchOptions: {
            aggregateTimeout: 300,
            poll: true
        },
    }));
    app.use(WebpackHotMiddleware(compiler));
}

總結(jié)

到此,我們完成了webpack打包和啟動文件配置乘凸,結(jié)合上一篇文章的內(nèi)容厕诡,目前的項目文件地址在這里

使用方法:

git clone https://github.com/sam408130/react-blog.git
git checkout part3
npm install 
npm start

啟動后,瀏覽器自動打開博客的首頁:

屏幕快照 2017-11-02 下午6.48.42.png

下一節(jié)內(nèi)容介紹如何使用redux营勤,使用redux-saga處理異步請求的action灵嫌,以及所有頁面的數(shù)據(jù)請求和頁面更新。

系列文章

React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 1 博客頁面展示
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 2 后臺管理頁面
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 3 Express + Mongodb創(chuàng)建Server端
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 4 使用Webpack打包博客工程
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 5 使用Redux
React技術(shù)棧+Express+Mongodb實現(xiàn)個人博客 -- Part 6 部署

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冀偶,一起剝皮案震驚了整個濱河市醒第,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌进鸠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形病,死亡現(xiàn)場離奇詭異客年,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漠吻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門量瓜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人途乃,你說我怎么就攤上這事绍傲。” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵烫饼,是天一觀的道長猎塞。 經(jīng)常有香客問我,道長杠纵,這世上最難降的妖魔是什么荠耽? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮比藻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己候址,他們只是感情好盖矫,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著务蝠,像睡著了一般插爹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上请梢,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天赠尾,我揣著相機(jī)與錄音,去河邊找鬼毅弧。 笑死气嫁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的够坐。 我是一名探鬼主播寸宵,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼元咙!你這毒婦竟也來了梯影?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庶香,失蹤者是張志新(化名)和其女友劉穎甲棍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赶掖,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡感猛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奢赂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陪白。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膳灶,靈堂內(nèi)的尸體忽然破棺而出咱士,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布序厉,位于F島的核電站锐膜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脂矫。R本人自食惡果不足惜枣耀,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庭再。 院中可真熱鬧捞奕,春花似錦、人聲如沸拄轻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨搓。三九已至院促,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斧抱,已是汗流浹背常拓。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辉浦,地道東北人弄抬。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像宪郊,于是被迫代替她去往敵國和親掂恕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容