说明
玩转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
8、打开浏览器访问 http://localhost:3000/search
, 就可以看到下面效果