webpack4 + ejs + urllib-sync 构建注重SEO的多页面应用

导言

前段时间写了一个官网,因为是官网所以比较注重SEO,但官网又是多语言,不同语言的meta又不同,为了方便爬虫爬取信息所以用到webpack打包

明确需求

  • 1.官网是多页面且多语言,因此要用webpack打包成多页面应用
  • 2.使用ejs模板语言编写,将公共部分(如:nav,footer等)独立成单独的组件
  • 3.ejs不同组件之间相互传递数据
  • 4.用urllib-sync同步从数据库获取数据
  • 5.将动态的数据添加到页面中生成填充完数据的静态文件之后再打包

开始项目

1.目录结构

webpack+ejs
├── src
│    ├── chain
│    ├── index
│    └── tpl
│         ├── app
│         ├── chainContent
│         ├── foot.ejs
│         └── nav.ejs
├── static
│     ├── css
│     ├── img
│     ├── js
│     └── lang
├── config.js
├── package.json
├── webpack.base.js
├── webpack.dev.js
└── webpack.pro.js

2.首先配置webpack

webpck.base.js(只显示其中一小部分的配置)
//利用glob.sync实现多页面打包
const glob=require('glob-all');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

//引入语言包
const i18n={
  cn:require('./static/lang/cn.js'),
  en:require('./static/lang/en.js'),
  ko:require('./static/lang/ko.js')
}

//动态设置entry,htmlwebpackplugin
const setMPA=()=>{
    const entry={};
    const HtmlWebpackPlugins=[];
    const entryFiles=glob.sync(path.join(__dirname,'./src/*/index.js'));
    Object.keys(entryFiles).map((index)=>{
        const entryFile=entryFiles[index];
        const match=entryFile.match(/src\/(.*)\/index\.js/);
        const pageName=match&&match[1];
        entry[pageName]=entryFile;
        Object.keys(i18n).map(lang=>{
            HtmlWebpackPlugins.push(
                new HtmlWebpackPlugin({
                    filename: path.join(__dirname, `dist/${lang}/${pageName}.html`),//输出文件名
                    template: path.join(__dirname,`src/${pageName}/index.html`),//以当前目录下的index.html文件为模板生成dist/index.html文件
                    chunks:[pageName],//我的理解是一个入口就是一个chunk
                    inject: true,//是否注入css,js,true表示在打包完成的 [pageName].html 内自动引入 chunks 为 [pageName] 的css,js
                    minify: { //压缩html
                      removeComments: true,
                      collapseWhitespace: true,
                      removeAttributeQuotes: true,
                      minifyCss: true, //——>此处的minifyCss/js是为了压缩[pageName].html页面内的内嵌css/js,并不会对外链的css/js有影响
                      minifyJs: true
                    },
                    // 通过CommonsChunkPlugin持续处理多个块的必要条件
                    chunksSortMode: 'dependency',
                    i18n: i18n[lang]//语言包
                })
            )
        })
    })
    return {entry,HtmlWebpackPlugins}
}
const {entry,HtmlWebpackPlugins} = setMPA();

module.exports = {
  mode: "production", //开发模式还是生产模式
  entry: entry ,//入口文件
  output: { //出口文件
    filename: '[name].[chunkhash].js',
  },
  plugins:HtmlWebpackPlugins,
  module: { //模块配置,主要用于一些loader的使用,用于转换编译css,ejs,图片等文件
    rules: [
      {
        // 对模版文件使用loader,虽然webpack默认支持ejs语法,但是如果将ejs转成静态页面则需要ejs-loader
        test: /\.ejs$/,
        use: 'ejs-loader'
      },
      // ......此处省略其他配置......
    ]
  }
}

3.页面中使用ejs语法:

index.html
<meta name="keywords" content="<%= htmlWebpackPlugin.options.i18n.keywords%>">
<meta name="description" content="<%= htmlWebpackPlugin.options.i18n.description%>">

4.将公共的组件分离出来

像 nav 和 footer 等都是公用部分,因此可以写成单独的组件,在各自页面中引入,具体用法:

footer.ejs
<footer>
  <div class="container">
    <a href="<%=htmlWebpackPlugin.options.i18n.link%>" target="_blank"><img src="../static/img/foot/[email protected]" width="32">a>
  div>
footer>

上面footer中用到了htmlWebpackPlugin因此在引入此组件时这样用:

index.html:父组件向子组件传参

<%= require('foot.ejs')({htmlWebpackPlugin}) %>

但是还遇到一个困惑就是如果从后台异步获取的数据怎么渲染到页面上呢???那么接下来就解决这个问题

5.异步获取的数据渲染到页面上

方案一:
tpl.ejs
<% if (info) { %>
  <meta name="description" content="<%=info.description%>">
  <title>
    <%=info.title %>
  title>
<% } %>
chainContent/index.js
import meta from './meta.ejs';
export default function ContentTpl(data) {
  return {
    meta: meta(data)
  };
}
src/chain/chain.html
<head> head>
src/chain/chain.js
import ContentTpl from 'chainContent/index.js';

$.get(url, (res) => {
  const tpl = new ContentTpl({ info: res.data });
  // 将组件追加到head里
  $('head').append(tpl.meta);
}, 'JSON');

到此本以为要结束了,正喜滋滋的准备发生产时,突然发现打包后的文件中head里的descriptiontitle并不是存在源码中的,需要异步从后台获取后才能追加到页面中,这样还是不利于SEO。

所以寻找用同步方法实现从数据库获取数据,并将动态的数据添加到页面中生成填充完整的文件之后再打包成静态文件,于是发现了urllib-sync

方案二:urllib-sync
npm i urllib-sync -D
urllib-sync的基本用法
// 同步请求数据方法
const request = require('urllib-sync').request;
let chains = request('URL').data.toString()
const supportChain = JSON.parse(chains).data
项目中webpack.base.js的补充配置
// 同步请求数据方法
const request = require('urllib-sync').request;
// 将markdown转为HTML
const HyperDown = require('./static/js/Parser')
const parser = new HyperDown();

//动态设置entry,htmlwebpackplugin
const setMPA = () => {
  const entry = {};
  const HtmlWebpackPlugins = [];
  // ---------此处省略其他入口配置-----------------

  // 入口配置
  entry['chain'] = path.join(__dirname, './src/chain/chain.js');
  // 在打包前获得数据
  supportChain.map((item) => {
    Object.keys(i18n).map(lang => {
      // 用同步方法实现从数据库获取数据
      let chain_info_str = request('URL' + item.ID + '&lang=' + lang).data.toString()
      let chain_info = JSON.parse(chain_info_str).data
      // 此处将markdown格式的文本转为HTML
      if (chain_info.content) {
        chain_info.text = parser.makeHtml(chain_info.content)
      }
      HtmlWebpackPlugins.push(
        new HtmlWebpackPlugin({
          filename: `${lang}/${item.name}.html`,
          template: path.join(__dirname, `src/chain/chain.html`),
          chunks: ['chain'],
          // ----此处省略其他配置----
          //此处将获取到的内容传到页面中,在页面中即可用htmlWebpackPlugin.options.chainInfo去填充
          chainInfo: chain_info,
        })
      )
    })
  })

  return { entry, HtmlWebpackPlugins }
}

然后执行 npm run build 打包完成,搞定!

到此本文即将结束,如果觉得没看懂可以查看下方?

本文详细源码可戳此处进行查看

我的个人博客,有空来坐坐

你可能感兴趣的:(Webpack,JS)