vue-cli3结合webapck优化

Posted 面条请不要欺负汉堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue-cli3结合webapck优化相关的知识,希望对你有一定的参考价值。

vue-cli3结合webapck优化

1 . 懒加载

(1). js 需要的时候加载

app.vue:
<template>
    <div id="app">
        <button @click="fn">按钮</button>
    </div>
</template>
<script>
export default {
    name: "App",
    created() {
        console.log('index.js  被加载')
    },
    methods: {
        fn() {
            import("../src/utils/test.js")
                .then(({ mul }) => {
                    console.log('test.js加载成功:',mul(40, 3));
                })
                .catch((err) => {
                    console.log(`test.js加载失败:${err}`);
                });
        }
    },
};
</script>

test.js
console.log('test.js 被加载')
export function mul(a,b){
    return a+b
}

点击按钮的时候,才会加载test.js 文件。不会重复加载,当第二次点击按钮会调用缓存。

(2). 路由懒加载

静态引用方式
import KeepAlive from '@/components/KeepAlive'
routes:[ path: '/', name: 'KeepAlive', title: "iframes页面",component: KeepAlive ]

改成这种 

{
    path: "/",
    name: "KeepAlive",
    title: "iframes页面",
    component: () => import("../component/KeepAlive.vue")// 这种就是赖加载
},

以函数的形式动态引入,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件

补充:有时候需要解析识别import(),配合使用babel-plugin-syntax-dynamic-import

babel-plugin-syntax-dynamic-import 作用:用以解析识别import()动态导入语法---并非转换,而是解析识别

安装:

npm install babel-plugin-syntax-dynamic-import

使用:.babelrc

{
  "plugins": ["syntax-dynamic-import"]
}

2. 预加载

提前知道用户未来可能会访问的内容 ,就会把那些内容路由 提前加载。
文件正常加载可以认为是并行加载(同一时间加载多个文件),预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源

在之前的import语法中添加一个参数 webpackPrefetch 表示预加载
document.getElementById('btn').onclick = function () {
    // 预加载
    import(/* webpackChunkName: 'test', webpackPrefetch: true */ "../src/utils/test.js")
        .then(({multi, sum}) => {
            console.log(multi, sum)
            console.log('multi=', multi(3, 3))
        }).catch(() => {
            console.log('test文件加载失败!')
        })
}

注意:可以关闭预加载的功能

module.exports = {
    chainWebpack: config => {
        // 移除 prefetch 插件
        config.plugins.delete('prefetch')
    }
}

(1).懒加载和预加载在实现上面都差不多,但是区别是:

预加载会在使用之前提前加载test文件
懒加载是当文件需要用时才加载

(2). 正常加载和预加载的区别:

正常加载可以认为是并行加载(同一时间加载多个文件)并没有所谓的先后顺序。会造成某一个文件很大但是它是在某些特定条件下才会实现,那么就会造成整体页面加载缓慢。
预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。(预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。)

(3). 懒加载和预加载的缺点:

懒加载:首次点击按钮才加载文件,但是文件过大就会等待时间过长页面才会有反应,(相当于有一个延迟的效果)用户体验不好。但是第二次点击就会调用缓存,就没问题了。
预加载:会有兼容问题(ie浏览器会有很大的兼容问题),慎用。

3. 使用cdn 预加载, 提前 引入js 文件

一种方式在public/index.html 引入
另一种方式 结合 vue.config.js

vue.config.js
// cdn预加载使用
const externals = {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex',
    'axios': 'axios'
}
const cdn = {
    // 开发环境
    dev: {
        css: [
            'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
            'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
        ],
        js: []
    },
    // 生产环境
    build: {
        css: [
            'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
            'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
        ],
        js: [
            'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
            'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js',
            'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
            'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js',
        ]
    }
}
module.exports = {
    chainWebpack: config => {
        // 使用cdn
        config.plugin('html').tap(args => {
            if (process.env.NODE_ENV === 'production') {
                args[0].cdn = cdn.build
            }
            if (process.env.NODE_ENV === 'development') {
                args[0].cdn = cdn.dev
            }
            return args
        })
    },
    configureWebpack: config => {
        const myConfig = {}
        //本地环境 线上环境
        if (process.env.NODE_ENV === 'production') {
            myConfig.externals = externals
        }
        if (process.env.NODE_ENV === 'development') {
            myConfig.devServer = {
                disableHostCheck: true
            }
        }
        return myConfig
    }

}

 public/index.html:

<!DOCTYPE html>
<html lang="zh-CN">
 
<head>
  <meta charset="utf-8">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
 
  <!-- 使用CDN加速的CSS文件,配置在vue.config.js下 -->
  <% 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文件,配置在vue.config.js下 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
  <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
  <% } %>
 
  <!-- 测试 -->
  <title>vue-cli3</title>
</head>
 
<body>
 
  <noscript>
    <strong>We're sorry but vue-project-demo doesn't work properly without javascript enabled. Please enable it
      tocontinue.</strong>
  </noscript>
  <div id="app"></div>
 
  <!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  <% } %>
 
</body>
</html>

4. 去掉注释、去掉console.log

npm i uglifyjs-webpack-plugin

vue.config.js :

const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 
module.exports = {
    configureWebpack: config => {
            const myConfig = {}
            if (process.env.NODE_ENV === 'production') {
                myConfig.plugins = []
                myConfig.plugins.push(
                new UglifyJsPlugin({
                    uglifyOptions: {
                    output: {
                        comments: false, // 去掉注释
                    },
                    compress: {
                        warnings: false,
                        drop_console: true,
                        drop_debugger: false,
                        pure_funcs: ['console.log']//移除console
                    }
                    }
                })
            )

        }
    }
}

5. Gzip压缩(压缩js、css)

npm i compression-webpack-plugin

const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
    configureWebpack:{
        plugins: [
			// 配置compression-webpack-plugin压缩
			new CompressionWebpackPlugin({
				algorithm: 'gzip',
				test: new RegExp('\\\\.(' + productionGzipExtensions.join('|') + ')$'),
				threshold: 10240,
				minRatio: 0.8
			})
		]
    }

}

6. css 不拆分

vuecli 3会默认开启一个css分离插件 ExtractTextPlugin

每一个模块的css文件都会分离出来,整整13个css文件,而我们的首页就请求了4个,花费了不少的资源请求时间,
所以可以在vue.config.js中关闭它
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: false,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
},
这样的话,打包出来的dist 就没有css 文件

组件重复打包

二 webpack

识别你的 入口文件,识别你的模块依赖,来打包成一个js文件

1. externals 外部扩展-- 引入cdn

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖。总就是:防止将某一些包打包到我们最终的bundle里面导致包体积变大【不想通过webapck 打包】

场景:jquery我们希望通过csdn链接引入使用,不希望打包到最后的bundle里面,为了避免打包的时候将jquery最终也给打包了,可以配置externals,禁止将jquery打包。可以参考【使用cdn 预加载, 提前 引入js 文件】
注意: 必须再外网

2. plugins

指webpack 开发 内置的插件【如:compression-webpack-plugin】,而不是其他第三方的插件

3. bundle.js

bundle.js 简单的理解是打包生产的js文件

依赖关系:
因为webpack从entry开始,对每一个 module 都进行处理,碰到 require 之后就跳入到对应的 module 的处理,也就是递归的对这颗依赖树进行处理,这是典型的深度优先遍历的递归解法,而且是先序优先遍历。处理的过程是这样的
处理 main.js,记录入口 [main]
碰到 require(a),记录 [main, a]
进入到 a 模块,碰到语句 require©, 记录下来 [main, a, c]
同理碰到 require(d),记录下来 [main, a, c, d]
返回到 main.js,下一句是 require(‘b’),记录下来 [main, a, c, d, b]
进入模块 b,碰到语句 require(e),记录下来[main, a, c, d, b, e]
返回,结束

4. prefetch(预先加载模块)

预加载prefetch:会在使用之前,提前加载js;

文件正常加载可以认为是并行加载(同一时间加载多个文件),预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。

兼容性比较差,慎用。

5. HMR(hot module replacement) 模块热替换

作用:一个模块发生改变,只会重新打包这一个模块(而不是打包所有模块),提高webpack构建速度
实现:在devServer配置中增加一个hot属性,值为ture,表示开启HMR功能。HMR是基于dev-server的,生产环境是不需要dev-server。

devServer: {
    contentBase: resolve(__dirname, 'build'),
    port: 3000,
    open: true,
    compress: true,
    hot: true, // 开启HMR功能  
}

注意:

样式文件:可以使用HMR功能,因为style-loader内部实现了HMR功能,会自动的去做。所以我们在开发环境借用style-loader,性能更好,打包速度更快;生产环境需要提取成一个单独的文件,因为上线需要考虑到代码的性能优化。
js文件: 默认不能使用HMR功能,修改完js代码后,整体模块会重新加载
html文件:默认不能使用HMR功能

6. babel缓存 & hash缓存

场景:

当有100个js文件,当修改了其中某一个js文件的时候,不可能再把所有的js文件再编译一遍,会影响构建速度,所以这个时候只要实现修改一个js文件只构建那一个文件,其他的99个文件不动。

那么这个时候可能会想到用热模块替换(HMR),但是HMR是基于dev-server的,生产环境是不需要dev-server。
那么这个时候可以用缓存实现。怎么配置呢?
1、针对js兼容性进行缓存:(babel缓存)
babel-loader的options设置中增加cacheDirectory属性,属性值为true。表示:开启babel缓存,第二次构建时会读取之前的缓存,构建速度会更快一点。

{
    test: /\\.js$/,
    loader: 'babel-loader',
    options: {
        cacheDirectory: true
    }
}

2、针对文件资源进行缓存
(1)hash:每次webpack构建时都会生成一个唯一的hash值。(不管文件有没有修改,只要构建就重新生成hash值。)
比如:filename: ‘built.[hash:10].js’
缺点:因为webpack构建js和css是共用一个hash值,如果重新打包会导致所有缓存失效。(只修改js文件,那么css文件也重新构建了)
(2)chunkhash:如果打包来自同一个chunk,那么hash值就一样。
问题:js和css的hash值还是一样的
因为css是在js中被引入的,所以还是同一个hash值,同属于一个chunk.
(3)contenthash:根据文件的内容生成hash值,不同文件的hash值都不一样

缓存作用

babel缓存:让第二次打包构建速度更快
hash缓存:让代码上线运行缓存更好使用

7. 代码分割 code split

将我们打包输出的一个文件分割为多个文件。(并行加载,速度更快 、按需加载)
主要针对于js代码。

(1)方式一

通过修改entry配置多入口,有几个入口输出有几个bundle

entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
}

缺点:修改不太灵活,要改配置。

(2)方式二:optimization

  1. 单入口应用中
    可以将node_modules中代码单独打包成一个chunk最终输出(将第三方的东西打包成一个chunk,自己本身的代码打包成一个chunk)
  2. 多入口应用中
    自动分析多入口chunk中有没有公共的文件(公共依赖),如果有会单独打包成一个chunk
optimization: {
    splitChunks: {
        chunks: 'all'
    }
}

(2)方式三:import动态导入语法,

import动态导入语法,能将某个文件单独打包(通过js代码让某个文件被单独打包成一个chunk) (纯粹的单入口)

// 在index.js文件中通过import动态引入test.js文件:
import(/* webpackChunkName: 'test' */'./test')
    .then(({multi, sum}) => {
        console.log('res=', multi(1, 2), sum(3, 5))
    }).catch(() => {
        console.log('文件加载失败')
})

8. 多进程打包 thread-loader

场景:js是单线程,同一时间只能干一件事情,如果事情很多,那么就会排队等很久,时间慢。如果同一时间两个/三个进程干这件事情,那么速度就会快很多。

thread-loader :开启多进程打包,一般用在babel-loader后面。

module: {
    rules: [
        {
            test: /\\.js$/,
            use: [{
                loader: 'thread-loader',
                options: {
                    workers: 2
                }
            },
            'babel-loader']
        }
    ]
}

有利有弊

进程启动大概600ms,进程通信(告诉你某件事情我干好了)也有时间开销。
假设我们花100ms完成的事情,开启多进程打包,那么就会得不偿失。所以多进程打包用在:工 作消耗时间比较长,用直白的话说就是js代码多

补充
多进程打包一般用在js文件上面,那么有两个loader对js文件进行处理:
eslint-loader:只是对代码进行语法检查,消耗时间可能不是很长
babel-loader:要进行编译,转换,所以时间比较长,所以用在babel-loader里面

9. dll

(1). dll是什么

类似于externals,指使哪些库是不需要打包的。和externals区别在于 dll 会单独的对某些库进行单独打包,将多个库打包成一个chunk。

(2). 有什么意义

正产情况下一个项目下 node_modules 里面的包会打包成一个chunk,但是第三方库很大,打包成一个chunk文件体积过大。所以通过 dll 将所有库进行单独打包成不同的chunk,更加有利于我们的性能优化,提高构建速度。

使用dll技术对某些库(第三方库:jquery,react,vue…)进行单独打包,减少重复打包次数,提高构建速度。

(3). 参考

webpack优化系列(11)-dll

9. webpack分包及异步加载套路

(1)静态资源图片,字体可以在webpack配置url-loader,file-loader结合这用,以及png,svg,base64这些结合使用,具体可以参考百度搜索
(2)js文件打包,可以使用chunk,进行分模块,并且webpack添加按需加载配置dynamic(比如一个文件超过1M,可以分成多文件,具体拆分规则待讨论)
(3)vue的每个组件中,对于引入的dom对象的使用,事件监听,或者bus的使用,记住,需要销毁,否则占用内存较大,久了容易页面卡顿
(4)可以在路由的加载中进行权限校验,但是复杂的权限,建议提取出来,项目登录时判断+进入主页后判断,并且可以对数据进行持久化,

以上是关于vue-cli3结合webapck优化的主要内容,如果未能解决你的问题,请参考以下文章

vue init webapck报错

vue-cli3 vue.config.js配置

基于vue-cli项目打包慢的定位优化过程

优化vue项目的首屏加载速度

Vue-cli3执行serve和build命令时nodejs 内存溢出问题

vue-cli脚手架npm相关文件说明build.js