內(nèi)容回顧
前面的篇幅主要介紹了:
- 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端
到目前階段图毕,你對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,在完成該部分時狞尔,記得在根路徑上配置.babelrc
和postcss.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)度
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-middleware
和webpack-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
啟動后,瀏覽器自動打開博客的首頁:
下一節(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 部署