关于优化的建议和配置补充

想了很久,在之前的webpack教程中,也仅仅只是教了大家,如何使用webapck4手撸一份vue可用的配置,但是在我的实际工作中,会遇到很多需要优化的地方,例如:cdn加速, gzip等这些操作,再加上,原本的那一份教程,在css和css预处理器的操作上,有很多的不足和细微的问题。现在一点点的捋出来,希望能够帮到大家。

  • 首先就是关于css和css预处理器的设置上,由于当时时间问题,很草率地就直接自己写上了,问题是没有问题,但是相比vue-cli2生成的那个配置,我还是有很多的不足,现在我就在vue-cli2的基础上,对其进行修改,接上篇的配置信息,我们还需要在utlis.js中添加对css的处理代码如下

关于部分css类的loader的优化方案

// 将css类loader放到这里一并处理,不再写在dev和prod中了
exports.cssLoaders = function (options) {
    options = options || {}

    const cssLoader = {
        loader: 'css-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }

    const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }

    // 这里就是生成loader和其对应的配置
    function generateLoaders(loader, loaderOptions) {
        const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

        if (loader) {
            loaders.push({
                loader: loader + '-loader',
                options: Object.assign({}, loaderOptions, {
                    sourceMap: options.sourceMap
                })
            })
        }

        // 当配置信息中开启此项时,启用css分离压缩
        // 这一项在生产环境时,是默认开启的
        if (options.extract) {
            return [MiniCssPlugin.loader].concat(loaders)
        } else {
            // 如果不开启则让vue-style-loader来处理
            return ['vue-style-loader'].concat(loaders)
        }
    }

    // https://vue-loader.vuejs.org/en/configurations/extract-css.html
    return {
        css: generateLoaders(),
        postcss: generateLoaders(),
        less: generateLoaders('less'),
        sass: generateLoaders('sass', { indentedSyntax: true }),
        scss: generateLoaders('sass'),
        stylus: generateLoaders('stylus'),
        styl: generateLoaders('stylus')
    }
}

// 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装
exports.styleLoaders = function (options) {
    const output = []
    const loaders = exports.cssLoaders(options)

    for (const extension in loaders) {
        const loader = loaders[extension]
        output.push({
            test: new RegExp('\\.' + extension + '$'),
            use: loader
        })
    }

    return output
}
  • 完整的utils.js呢,我也放上来吧,以免有的朋友不大知道这段代码该加在什么位置
'use strict'
const path = require('path')
const packageConfig = require('../package.json')
const config = require('../config')
// 这里为什么要引入这个呢,由于webpack4的原因,需要用到这个插件的loader
const MiniCssPlugin = require('mini-css-extract-plugin');

// 将资源文件合并到config文件中的配置项中
exports.assetsPath = function (_path) {
    const assetsSubDirectory = process.env.NODE_ENV === 'production'
        ? config.build.assetsSubDirectory
        : config.dev.assetsSubDirectory

    return path.posix.join(assetsSubDirectory, _path)
}
// 将css类loader放到这里一并处理,不再写在dev和prod中了
exports.cssLoaders = function (options) {
    options = options || {}

    const cssLoader = {
        loader: 'css-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }

    const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }

    // 这里就是生成loader和其对应的配置
    function generateLoaders(loader, loaderOptions) {
        const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

        if (loader) {
            loaders.push({
                loader: loader + '-loader',
                options: Object.assign({}, loaderOptions, {
                    sourceMap: options.sourceMap
                })
            })
        }

        // 当配置信息中开启此项时,启用css分离压缩
        // 这一项在生产环境时,是默认开启的
        if (options.extract) {
            return [MiniCssPlugin.loader].concat(loaders)
        } else {
            return ['vue-style-loader'].concat(loaders)
        }
    }

    // https://vue-loader.vuejs.org/en/configurations/extract-css.html
    // 返回对应的loader
    return {
        css: generateLoaders(),
        postcss: generateLoaders(),
        less: generateLoaders('less'),
        sass: generateLoaders('sass', { indentedSyntax: true }),
        scss: generateLoaders('sass'),
        stylus: generateLoaders('stylus'),
        styl: generateLoaders('stylus')
    }
}

// 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装
// 这里的options就是在调用的时候向内传递一些loader的配置信息,比如map文件等
exports.styleLoaders = function (options) {
    const output = []
    const loaders = exports.cssLoaders(options)

    for (const extension in loaders) {
        const loader = loaders[extension]
        output.push({
            test: new RegExp('\\.' + extension + '$'),
            use: loader
        })
    }

    return output
}
// 当出现报错时,弹出系统弹窗告知出现错误
exports.createNotifierCallback = () => {
    const notifier = require('node-notifier')

    return (severity, errors) => {
        if (severity !== 'error') return

        const error = errors[0]
        const filename = error.file && error.file.split('!').pop()
        // 这里是设置当出现错误时,给你的,标题,错误信息,错误文件地址,以及图标
        notifier.notify({
            title: packageConfig.name,
            message: severity + ': ' + error.name,
            subtitle: filename || '',
            // icon: path.join(__dirname, 'logo.png')
        })
    }
}

// 获取本地局域网ip
exports.getNetworkIp = () => {
    const os = require('os');
    let needHost = ''; // 打开的host
    try {
        // 获得网络接口列表
        let network = os.networkInterfaces();
        for (let dev in network) {
            let iface = network[dev];
            for (let i = 0; i < iface.length; i++) {
                let alias = iface[i];
                if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                    needHost = alias.address;
                }
            }
        }
    } catch (e) {
        needHost = 'localhost';
    }
    return needHost;
}
  • 然后我们需要在原webpack.dev.js和webpack.prod.js中去修改我们之前的rules:
    //  开发环境下
    ...
    mode: 'development',
    devtool: config.dev.devtool,
    module: {
        // 将之前的rules数组直接替换成下面的样子
        rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
    },
    output: {
        filename: '[name].js',
    }
    ...
    // 生产环境下
    mode: 'production',
    devtool: config.build.productionSourceMap ? config.build.devtool : false,
    module: {
        // 将之前的rules数组直接替换成下面的样子
        rules: utils.styleLoaders({
            // 是否开启css的map映射
            sourceMap: config.build.productionSourceMap,
            // 该选项是是否开启css分离压缩的选项
            extract: true,
            // 是否使用postcss-loader添加浏览器前缀
            usePostCSS: true
        })
    },
    output: {
        path: config.build.assetsRoot,
    },
    ...
  • 好啦,大家应该就很清楚了,在这里我们使用了utlis中的styleLoaders函数,由上面的代码我们就就可以知道,该函数接收至多三个对象 sourceMapextractusePostCSS;他们均是一个布尔值,从左到右分别表示,是否打开css的map文件,是否开启css抽取压缩,是否为一些css样式添加浏览器前缀。至此,我们就可以彻底的放心大胆的使用市面上已有的css预处理器,因为在utlis中的cssLoaders函数中,我们看到,它会返回你设置好的所有的预处理器loader,这样我们就不用再两个webpack文件中都要去设置,只需要当出了新的css预处理器的时候,加到这里就好了。

当依赖库十分庞大时,dev缓慢的问题

很多时候,项目的依赖非常的多,这就导致了每次更改之后热更新需要等很久的时候,那么此时,我们就需要使用dll来解决这个问题了,当然,和win上的dll文件是有很大的区别的,我们需要使用的是 webpack自带的 DllPlugin插件,现在附上我的代码:


const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackbar = require('webpackbar')
module.exports = {
    mode: 'development',
    entry: {
        // 这里就是添加
        vendor: ['axios', 'element-ui', 'vue', 'vue-router', 'echarts']
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, 'dist'),
        library: '_dll_[name]',
    },
    plugins: [
        new CleanWebpackPlugin(),//clean dist
        new webpack.DllPlugin({
            name: '_dll_[name]',
            path: path.join(__dirname, 'dist', '[name].manifest.json'),
        }),
        new webpackbar({
            name: "抽取依赖中",
        })
    ],
};
  • 然后我们在package.json中添加脚本 "dll":"webpack --config webpack.vendor.js"在每次添加了大型依赖之前可以将其加入vendor数组内,并且执行 npm run dll,这样每次编译的时候,就不会带上那些大型依赖,也就不会导致每次更改一个字编译都要等很久的囧场景了~

关于cdn加速的一些建议

随着需求越来越多,各种依赖库也越来越多,如果是跨平台应用还好,资源加载都在本地,不需要用户从我们服务器下载,但是,线上的站怎么办,我们的服务器带宽有限,压根就没法顾及这些,此时,cdn会给我们一些帮助,使用cdn可以将我们的一些依赖放在他们的服务器中分发,好处不言而喻,在去除了依赖库的情况下,我们的代码再加上进行了压缩,分包等一系列处理,不仅仅只是缩短了首屏启动的时间,那么现在来贴代码:

// 在原打包分析下添加或者除配置对象中的任意位置
// 这里有涉及到config文件夹中index文件的build对象
// 那么我们肯定也要在config文件中做对应的设置了
if (config.build.cdn) {
    // 此处是从config中读取NeedCdnModuleName
    options.externals = config.build.NeedCdnModuleName
    // 这里按需加载了一个依赖,所以,您需要安装它
    const WebpackCdnPlugin = require('webpack-cdn-plugin');
    options.plugins.push(new WebpackCdnPlugin({
        modules: config.build.NeedCdnModuleAddress
    }))
}
  • 关于config文件夹中index文件的内容则是在其build对象下新添代码
build:{
        // 此处配置可使用插件自动配置需要cdn加速的模块
        // 你可能需要执行 npm i webpack-cdn-plugin -D 命令来安装这个依赖
        cdn: false,
        // 需要说明的是,这个里,用于配置需要被webpack忽略的依赖包名
        // 左边是npm安装时的名字,而右边则是该包对全局暴漏的名字
        // 如果不知道全局名称怎么办,
        // 详情配置请参考:https://webpack.docschina.org/configuration/externals/
        // 该插件默认走的是https://unpkg.com cdn服务器
        NeedCdnModuleName: {
            vue: 'Vue',
            'vue-router': 'VueRouter',
        },
        // 这里则是被webpack忽略之后,加载cdn
        // name表示该模块在npm中的命名,var表示该模块全局变量名称,应当和上面右侧设置一致
        // path则代表是该模块在unpkg中的文件路径,如果不清楚清去该站点自行查询。
        // 详情配置请参考:https://www.npmjs.com/package/webpack-cdn-plugin
        NeedCdnModuleAddress: [{
            name: 'vue',
            var: 'Vue',
            path: 'dist/vue.runtime.min.js',
        }, {
            name: 'vue-router',
            var: 'VueRouter',
            path: 'dist/vue-router.min.js',
        }],
}

这里需要提一下,在vue中,当你在main中引入了依赖库的css时,如果你没有判断就直接使用import的话,css则部分时没有加入cdn的,这里需要注意一下,在demo中,我会追加跟多本文中额外的信息,毕竟很多东西只看文章还是太抽象,结合代码学习,是最快的。

关于启用gzip

有时候,我们即使使用了cdn加速,剔除了所有的依赖,但是我们站本身的代码就非常的庞大,分包之后依旧还有100k以上的大小时,(可能你们会说100k没什么嘛,可以忽略了,那么这个就可以忽略查看)我们可以启用gzip,将我们的代码放到压缩包中在传到用户计算机上时,由浏览器解压执行;此时我们需要使用 compression-webpack-plugin,该插件支持三种压缩算法 brotliCompressgzip 还有 zopfli 至于哪种更好,就不在本文的阐述范围内了,上代码:

// 您可以选择添加在cdn下面
// 当config中对应项为true时,启用gzip功能
if (config.build.gzip) {
    const CompressionPlugin = require('compression-webpack-plugin');
    options.plugins.push(new CompressionPlugin({
        // 这里如果算法为brotliCompress的话,需要将gz改为br
        filename: '[path].gz[query]',
        // 压缩算法,这里的话,你可以选择brotliCompress和gzip还有zopfli,当然这一切要取决于用户的浏览器支持哪种压缩模式
        // 若要使用brotliCompress算法,需要nodejs11.7以上.并且后缀名为.br
        algorithm: 'gzip',
        // 根据config文件设置的情况对什么文件进行压缩
        test: new RegExp(
            '\\.(' +
            config.build.productionGzipExtensions.join('|') +
            ')$'
        ),
        // 超过该大小就会压缩(单位:字节)
        threshold: 10240,
        // 压缩比,官方给的解释设置0.8是最佳了.至于计算公式,就是minRatio = 压缩后的大小 / 原来的大小
        // 当压缩比比这个低的话,就会进行压缩处理,如果设置成1的话,就是全部都处理了.
        minRatio: 0.8,
        // 是否删除原文件(默认也是false)
        // 这这里并不推荐删除源文件,因为需要照顾到不支持gzip的浏览器
        deleteOriginalAssets: false,
    })
    )
}
  • 同样附上config中的代码
// 此处配置是是否使用gzip功能对代码进行高强度的压缩(值得注意的是gzip功能需要nginx做出对应的配置,谨慎开启)
// 你可能需要执行 npm i compression-webpack-plugin -D 命令来安装这个依赖
// 因为本包中可能会不带这项依赖,毕竟不是每个人都需要
gzip: false,
// 这里设置需要被压缩的文件后缀默认是只压缩css和js
productionGzipExtensions: ['js', 'css']
  • 这里,注释中也说明了,需要nginx或者您所知道您项目中所使用承载页面具备 httpServer 的服务器中去配置,开启gzip或是其他的压缩选项。

关于控制台的友好化输出

在Nuxt中我发现其控制台的进度条不仅仅可以给开发者一个进度告知,并且还会显示当前正在加载什么,幸运的是,他们将这个插件开源了出来,废话不多说,开整!

  • 首先是安装该插件 npm i webpackbar -D 然后在我们的webpack公共配置中加入
const webpackbar = require('webpackbar')
/*这里是其他代码*/
// 在插件数组中加入
plugins: [
    // 该插件支持一个名称,可用于控制台输出
        new webpackbar({
            name: "",
        })
    ],

然后我们重启一次webpack,就看到对应的效果了。其实还有更多的webpack的操作台输出插件,但是它们在win上表现非常差,所以我就不再介绍~

本系列教程已经综合到github中,您可以访问这里,查看完整文档,以及示例代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 人到中年 有些人说,人到中年,人生不再有梦想,于是便每天重复“昨天”的故事。美国管理学家德鲁克却强调,人的一生要不...
    蒋铭国江西乐平阅读 173评论 0 1
  • 九年(公元204年) 1、 春,正月,曹操渡过黄河,阻遏淇水,将河水引入白沟,作为运输粮草的运河。 二月,袁尚再次...
    华杉2009阅读 1,516评论 1 12
  • 今天第一个任务,就是助那无用补天之石,让他去富贵乡里经历一番。而第二个任务是什么呢?请往下看: 1、和尚道士下世的...
    向阳春丽阅读 267评论 0 0
  • 818高三寄语9月16日 【高考倒计时】今天是2019年9月16日,距离2020年高考还剩265天! 孩子成长的每...
    快乐星猫_55fd阅读 435评论 0 1
  • 初识三毛: 我清晰的记得,拿起三毛的第一本书是《雨季不再来》,文中尽数描写了三毛逃课、去坟墓堆等地方只是为...
    忆听雨轩阅读 856评论 2 11