Vue多页面优化踩坑记录

7,852 阅读6分钟

最近优化之前的项目,从单页面拆分成了多页面,这里记录下整个踩坑过程。

拆分成多个页面

1.将原项目的单页面替换为多页面


这里我添加了index.htmluser.html两个页面。

2.配置vue.config.js

// vue.config.js
const titles = require('./title.js')
const glob = require('glob')
const pages = {}

glob.sync('./src/pages/**/main.js').forEach(path => {
    const chunk = path.split('./src/pages/')[1].split('/main.js')[0]
    pages[chunk] = {
        entry: path,
        template'public/index.html',
        title: titles[chunk],
        chunks: ['chunk-vendors''chunk-common', chunk]
    }
})
module.exports = {
    pages,
    chainWebpackconfig => config.plugins.delete('named-chunks'),
    devServer: {
        proxy: {
            '/api': {
                target'http://127.0.0.1:8080',
                changeOrigintrue,
                pathRewrite: { '^/api''' }
            }
        }
    }
}

可以执行vue inspect查看完整配置信息:

路由懒加载

vue-router官方给出的示例如下,这里webpackChunkName如果不写打包时会自动生成序号代替。

//router/index.js
{
    path'/about',
    name'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },

为了方便追踪打包情况最好写上,就可以看到about.[hash].js的大小了。

如果想要多个路由打包进一个js里,写同一个webpackChunkName即可

  {
    path'/another',
    name'another',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/Another.vue')
  }

打包后about.js文件变大了0.33Kb,多了一个页面。

引入代码分析BundleAnalyzerPlugin

npm i webpack-bundle-analyzer后修改vue.config.js配置

//vue.config.js
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;
const report = process.env.npm_config_report; 
module.exports = {
  ...
  configureWebpack: config => {
    if (report) {
      config.plugins.push(new BundleAnalyzerPlugin());
    }
  },
  ...
 }

再在package.json中添加

  "scripts": {
    ...
    "analyze""npm_config_report=true npm run build"
  },

这里通过控制npm_config_report来显示分析页面,执行npm run analyze可以看到打包情况

提取第三方插件

上图可以看到vuevue-routervuex占据了大部分空间,它们和我们的实际开发无关,且不会经常变化,我们可以把它们单独提取出来,这样不用每次都重新打包,浏览器访问时因为并行加载和缓存会极大地提高访问效率。

常见的优化方法有两种:一是通过cdn搭配externals来实现,二是通过单独打包搭配DllReferencePlugin

简单说下两种优劣:
cdn+externals:配置简单,全环境下都可访问到cdn,如果不慎调用了打包文件内部的方法,可能会导致重复打包;
DllReferencePlugin:配置较复杂,需要手动生成dll,所有访问指向同一份dll,不会造成重复打包。

利用`externals`优化

1.简单的配置方法

最简单的做法就是将cdn手动添加到index.html

<body>
    <div id="app"></div>
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
    <script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js"></script>
    <script src="https://cdn.bootcss.com/vuex/3.1.1/vuex.min.js"></script>
</body>

然后在vue.config.js中说明externals

 configureWebpack: (config) => {
            config.externals = {
                vue'Vue',
                vuex'Vuex',
                'vue-router''VueRouter',
                // 'alias-name': 'ObjName'
                // 写法: 中划线: 上驼峰
            }
},

这时再打包就没有这些依赖项了。

2.区分开发环境和正式环境

以上方法配置后,因为引入的是vue.min.js,会导致Vue DevTool无法使用,怎么办呢?

const isProduction = process.env.NODE_ENV === "production";

...
configureWebpack: (config) => {
        if(isProduction){
               config.externals = {
                vue'Vue',
                vuex'Vuex',
                'vue-router''VueRouter',
                // 'alias-name': 'ObjName'
                // 写法: 中划线: 上驼峰
            }
        }
},

正式环境才使用externals可以吗?可以,但是报错:

Uncaught TypeError: Cannot redefine property: $router

因为在index.html中已经引入过一次vue-router了,如果不把它放入externals,就会重复定义。

因此我们需要仅在正式环境中引入cdn,调整之前的代码:

// vue.config.js
const cdn = {
    css: [],
    js: [
        'https://cdn.bootcss.com/vue/2.6.10/vue.min.js',
        'https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js',
        'https://cdn.bootcss.com/vuex/3.1.1/vuex.min.js',
    ],
}

module.exports={
    ...
    chainWebpack: (config) => {
        if (isProduction) {
          // 生产环境注入cdn + 多页面
          glob.sync("./src/pages/**/main.js").forEach(path => {
            const chunk = path.split("./src/pages/")[1].split("/main.js")[0];
            config.plugin("html-" + chunk).tap(args => {
              args[0].cdn = cdn;
              return args;
            });
          });
        }
    },
}

这一步在多页面模式下有个坑,官方有提到但是一句带过了——就是会存在多个 html-webpack-plugin实例,而config.plugin()必须接收准确的plugin名称,如果硬着头皮按照网上教程走,肯定会卡在这里。其实很简单,只要vue inspect --plugins就可以看到了

接下来,index.html处理下cdn参数就完成啦😝

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <!-- 使用CDN的CSS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
    <% } %>
    <!-- 使用CDN的JS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script" />
    <% } %>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vue-multiple-pages-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>

    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>

    <!-- built files will be auto injected -->
  </body>
</html>

利用`dllPlugin`优化

前面提到也可以用dllPlugin来优化,不过如果你使用chromeVue DevToolvue就不能放进dllPlugin了。

1.创建webpack.dll.conf.js

const path = require('path')
const webpack = require('webpack')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

// dll文件存放的目录
const dllPath = 'public/vendor'

module.exports = {
    entry: {
        core: ['vue-router','vuex'],
        // other: [],
    },
    output: {
        path: path.join(__dirname, dllPath),
        filename'[name].dll.js',
        // vendor.dll.js中暴露出的全局变量名
        // 保持与 webpack.DllPlugin 中名称一致
        library: '[name]_[hash]',
    },
    plugins: [
        // 清除之前的dll文件
        // "clean-webpack-plugin": "^1.0.0"  注意版本不同的写法不同
        // new CleanWebpackPlugin(['*.*'], {
        //     root: path.join(__dirname, dllPath),
        // }),
        // "clean-webpack-plugin": "^3.0.0"
        new CleanWebpackPlugin(),
        // 设置环境变量
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV'production',
            },
        }),
        // manifest.json 描述动态链接库包含了哪些内容
        new webpack.DllPlugin({
            path: path.join(__dirname, dllPath, '[name]-manifest.json'),
            // 保持与 output.library 中名称一致
            name: '[name]_[hash]',
            context: process.cwd(),
        }),
    ],
}

2.预编译dll
package.json中添加

script:{
    ...
    "dll""webpack -p --progress --config ./webpack.dll.conf.js"
}

运行npm run dll就可以在public/vendor下生成dll了。

3.在webpack中声明预编译部分
声明后webpack打包时就会跳过这些dll。

// vue.config.js

  configureWebpack: config => {
    if (isProduction) {
      config.externals = {
        vue"Vue"
        // vuex: "Vuex", 这些都改成dllPlugin编译
        // "vue-router": "VueRouter"
        // 'alias-name': 'ObjName'
        // 写法: 中划线: 上驼峰
      };
    }
    config.plugins.push(
        // 名称要和之前的一致,可以继续扩展多个
      ...["core"].map(name => {
        return new webpack.DllReferencePlugin({
          context: process.cwd(),
          manifestrequire(`./public/vendor/${name}-manifest.json`)
        });
      })
    );
  },

4.引用dll
最后就是引入到index.html中,你可以简单地直接写入:

    <script src=./vendor/core.dll.js></script>

如果想更智能些,就用用到add-asset-html-webpack-plugin,它能将生成在public/vendor下的dll自动注入到index.html中。

const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");

    config.plugins.push(
    ...
      // 将 dll 注入到 生成的 html 模板中
      new AddAssetHtmlPlugin({
        // dll文件位置
        filepath: path.resolve(__dirname, "./public/vendor/*.js"),
        // dll 引用路径
        publicPath: "./vendor",
        // dll最终输出的目录
        outputPath: "./vendor"
      })
    );
  },

大功告成!

最后

DEMO

地址:github.com/vita2333/vu…

每一个commit对应一个配置步骤,vue利用external引入,区分了开发和配置环境,其他的vue-router等都是用dllPlugin引入的,有需要的小伙伴可以去看看 :)

参考

github.com/Plortinus/v…
juejin.cn/post/684490…
blog.csdn.net/neoveee/art…