說明
玩轉webpack學習筆記
頁面打開過程
服務端渲染 (SSR) 是什麼?
渲染: HTML + CSS + JS + Data -> 渲染後的 HTML
服務端:
- 所有模闆等資源都存儲在服務端
- 内⽹機器拉取資料更快
- ⼀個 HTML 傳回所有資料
浏覽器和伺服器互動流程
用戶端渲染 vs 服務端渲染
總結:服務端渲染 (SSR) 的核⼼是減少請求
SSR 的優勢
1、減少⽩屏時間
2、對于 SEO 友好
SSR 代碼實作思路
服務端
- 使⽤
的react-dom/server
renderToString
⽅法将
React 元件渲染成字元串
- 服務端路由傳回對應的模闆
用戶端
打包出針對服務端的元件
實作 SSR
1、安裝依賴
npm i express -D
2、在項目根檔案添加檔案夾
server
,在檔案夾裡再添加
index.js
檔案
if (typeof window === 'undefined') {
global.window = {};
}
const fs = require('fs');
const path = require('path');
const express = require('express');
const { renderToString } = require('react-dom/server');
const SSR = require('../dist/search-server');
const template = fs.readFileSync(path.join(__dirname, '../dist/search.html'), 'utf-8');
const renderMarkup = (str) => {
return template.replace('<!--HTML_PLACEHOLDER-->', str);
};
const server = (port) => {
const app = express();
app.use(express.static('dist'));
app.get('/search', (req, res) => {
console.log('SSR-----------》', SSR);
console.log('renderToString(SSR)------>', renderToString(SSR));
const html = renderMarkup(renderToString(SSR));
res.status(200).send(html);
});
app.listen(port, () => {
console.log(`Server is running on port:${port}`);
});
};
server(process.env.PORT || 3000);
3、修改檔案夾 search 裡的 index.html,添加占位符
<!--HTML_PLACEHOLDER-->
<!DOCTYPE html>
<html lang="en">
<head>
<%= require('raw-loader!./meta.html') %>
<title>Document</title>
<script><%= require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js') %></script>
</head>
<body>
<div id="root"><!--HTML_PLACEHOLDER--></div>
<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react.min.js"></script>
<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react-dom.min.js"></script>
</body>
</html>
4、在檔案夾 search 裡添加 index-server.js 檔案,用于服務端渲染頁面
const React = require('react');
// 引入大加法
const kaimoLargeNumber = require('kaimo-large-number');
// 圖檔
const logo = require('./images/logo.png');
console.log(logo);
// 樣式
const s = require('./search.less');
console.log(s);
class Search extends React.Component {
constructor() {
super(...arguments);
this.state = {
Text: null
}
}
loadComponent() {
// 動态加在text.js,傳回一個promise
import('./text.js').then((Text) => {
console.log(Text);
this.setState({
Text: Text.default
});
})
}
render() {
const { Text } = this.state;
const kaimoLarge = kaimoLargeNumber('777', '666');
return <div className="search-text">
凱小默的部落格666
{ Text ? <Text /> : null }
大加法操作'777'+'666':{ kaimoLarge }
<img src={ logo.default } onClick={ this.loadComponent.bind(this) } />
</div>
}
}
module.exports = <Search />;
5、添加 webpack.ssr.js 配置檔案
const glob = require('glob');
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, './src/*/index-server.js'));
Object.keys(entryFiles)
.map((index) => {
const entryFile = entryFiles[index];
const match = entryFile.match(/src\/(.*)\/index-server\.js/);
const pageName = match && match[1];
if(pageName) {
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
inlineSource: '.css$',
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: ['vendors', pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
}
});
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins } = setMPA();
module.exports = {
entry: entry,
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]-server.js',
libraryTarget: 'umd'
},
mode: 'none',
module: {
rules: [
{
test: /.js$/,
use: [
'babel-loader',
'eslint-loader'
]
},
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
overrideBrowserslist: ['last 2 version', '>1%', 'ios 7']
})
]
}
},
{
loader: 'px2rem-loader',
options: {
remUnit: 75,
remPrecision: 8
}
}
]
},
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
}
]
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
}
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano')
}),
new CleanWebpackPlugin(),
new webpack.optimize.ModuleConcatenationPlugin()
].concat(htmlWebpackPlugins),
optimization: {
splitChunks: {
minSize: 0,
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 2
}
}
}
}
};
6、往 package.json 裡添加腳本
{
"scripts": {
"build:ssr": "webpack --config webpack.ssr.js"
}
}
7、運作
npm run build:ssr
,打包成功之後,然後再
node server/index.js