2
头图

1、背景

当前网站项目采用的是多页面,开发和重构分离,开发新页面时,因为重构开发可能经常会添加样式文件或者调整样式顺序,因此小组采用了如下开发模式
alt 当前构建流程图
随着页面越来越多,依赖的样式文件变多,gulp任务也随之变多,打包编译速度慢的问题逐渐暴露出来,这种模式下的打包时间截图如下:
alt 当前构建时间
可以看到gulp任务大约需要30s, webpack任务需要30s,加起来第一次构建需要1分多钟,而二次构建也需要8秒左右,想象每次构建脑袋中都跳出这个场景,这能忍受?可见已经到了必(ren)须(wu)要(ke)改(ren)的地步了好吗
alt 图片

于是决定修改当前构建方式,分为以下步骤进行

1、去掉gulp
修改后的模式如下图,其中绿色为新增流程,和原流程相比去掉了gulp打包css流程转而也交给webpack处理
alt 修改后流程图
切换到这种模式后,第一次打包时间大约20s,增量编译大概8s

2、webpack构建速度优化

2.1 exclude include优化

在匹配规则里指定需要处理和排除在外不处理的文件,缩小查找和处理文件范围,确保编译尽可能少的文件,针对项目情况添加对应的includeexclude配置,具体配置如下

rules: [
    {
        test: /\.[jt]s$/,
        include: [path.resolve(process.cwd(), 'src')],
        exclude: [/node_modules/, path.resolve(process.cwd(), 'src/vendor')],
    },
    {
        test: /\.css$/,
        include: RESOURCES.CSS.cwd,
    }
]

加上上面配置后,第一次打包时间大约16s,增量编译大概8s

2.2 thread-loaderesbuild-loader优化

thread-loader是采用多进程打包的方式,放在thread-loader后面的loader会单独的worker池里运行,
esbuild是由Go开发的用于打包压缩tsjs的工具,特点是打包速速很快,官方github介绍与webpack, rollup, Parcel相比较要快几十甚至上百倍,但是esbuild目前还不能支持css, 并且没有插件机制,所以目前暂时替代不了webpack,但是有用于webpack中的loaderesbuild-loader, 引入一下试一下效果, ViteSnowpack底层都采用了esbuild,参考thread-loader官方说明esbuild-loader官方说明后关键配置如下

const cpuNum = require('os').cpus().length;

const tsWorkerPool = {
    workers: 6,
    poolTimeout: Infinity
};

const cssWorkerPool = {
    workers: cpuNum - tsWorkerPool.workers,
    poolTimeout: Infinity
};

threadLoader.warmup(tsWorkerPool, ['esbuild-loader']);
threadLoader.warmup(cssWorkerPool, ['css-loader']);

module: {
    rules: [
        {
            test: /\.[jt]s$/,
            use: [
                {
                    loader: 'thread-loader',
                    options: tsWorkerPool
                },
                {
                    loader: 'esbuild-loader',
                    options: {
                        loader: 'ts',
                        target: 'es2015',
                        tsconfigRaw: require('../tsconfig.json')
                    },
                },
                {
                    loader: path.resolve(__dirname, 'loaders/importcss-loader.js'),
                    options: {
                        include: path.resolve(process.cwd(), 'src/app'),
                    }
                },
            ],
            include: [path.resolve(process.cwd(), 'src')],
            exclude: [/node_modules/, path.resolve(process.cwd(), 'src/vendor')],
        },
        {
            test: /\.css$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                },
                {
                    loader: 'thread-loader',
                    options: cssWorkerPool
                },
                {
                    loader: 'css-loader',
                    options: {
                        esModule: false,
                    }
                },
                {
                    loader: 'esbuild-loader',
                    options: {
                        loader: 'css',
                        minify: true
                    }
                }
            ],
            include: RESOURCES.CSS.cwd,
        }
    ],
},

经过测试,加上thread-loader后,第一次打包时间大约12s,增量编译大概3s,再加上esbuild-loader后,第一次打包时间缩短到8s左右,增量编译大概2s
注意: https://esbuild.github.io/api/#target文档中提到针对大部分语法esbuild-loader只支持转换到es6,因此只适合在开发环境使用,生产环境不建议使用

2.3 图片压缩和去掉冗余css样式文件

图片压缩配置如下

loader: 'image-webpack-loader',
options: {
    // 生产环境启用压缩
    disable: process.env.NODE_ENV === 'production' ? false : true,
    // 压缩 jpg/jpeg 图片
    mozjpeg: {
        progressive: true,
        quality: 80 // 压缩率
    },
    // 压缩 png 图片
    pngquant: {
        quality: [0.65, 0.90],
        speed: 4
    }
}

去掉冗余样式使用purgecss插件,配置如下, 需要针对特殊的不能去除的加上配置

new PurgecssPlugin({
    paths: glob.sync([
        path.resolve(__dirname, '../../server/views/**/*.html'),
        path.resolve(`${PATH.SRC}/**/*.vue`),
    ]),
    safelist: [
        /data-v-.*/, // vue scope样式保留
        /market-message/, // 处理营销通知栏背景颜色由配置决定导致需要剔除样式不确定的特殊处理
        /vip/, // 动态渲染vip等级样式保留
    ]
})

3、升级到webpack5

webpack去年发布了webpack5带来了很多优化,主要几点比如:
1、默认开启持久化缓存并缓存在内存中,而webpack4则需要借助cache-loaderhard-source-webpack-plugin来做缓存
2、NodeJSpolyfill脚本被移除, 而在webpack4及以前的版本中,对于大多数的Node模块将自动添加polyfill脚本,导致打包后体积会变大。
3、更好的TreeShaking
经过上面优化后,想进一步测试一下webpack5版本能带来的优化效果,将webpack包和相关依赖升级到最新版本,去掉不兼容的speed-measure-webpack-plugin和已经不需要的hard-source-webpack-plugin插件后,处理大概65个文件,第一次打包时间缩短到6.5s左右,增量编译时间1.5s左右,可以看到时间进一步缩短。
alt 增量编译
alt 增量编译

4、热加载和热更新

webpack.dev.js中添加以下配置

devServer: {
    contentBase: path.join(PATH.DIST, 'js'),
    inline: true,
    compress: true,
    port: 5000,
    writeToDisk: true,
    host: '0.0.0.0',
    hot: true,
    disableHostCheck: true,
    headers: {
        'Access-Control-Allow-Origin': 'https://www.midasbuy.com',
        'Access-Control-Allow-Headers': '*',
        'Access-Control-Allow-Methods': '*',
        'Access-Control-Allow-Credentials': true,
    },
},

因为我们开发时是用的whistle代理配合域名访问,在检测到文件文件更新时是通过websocket来进行消息通知的,因此需要在whistle中添加规则如下,第一条是建立websocket连接,第二条用于热更新时请求更新的内容,这里主要实现了vue组件的热更新和js的热刷新,css热跟新暂时没实现。

https://www.midasbuy.com:5000 http://127.0.0.1:5000/
^https://www.midasbuy.com/oversea_web/static/***hot-update***  http://127.0.0.1:5000/oversea_web/static/$1hot-update$2

坑:测试热刷新时,修改了文件,页面刷新后展示如下报错

然后再次手动刷新就好了,半天没找到原因,一度还怀疑是whistle代理的原因,之后怀疑是发送通知更新指令太早了,然后写了一个hack方法来延迟发送更新指令,发现可以了,但是具体原因还没找到,直到切换到服务端终端,更新文件时看到了如下打印
alt 服务端终端打印信息
才明白了,我们的流程是client端编译好文件后,会将文件复制到server文件夹下面来提供给ejs include进来使用,server使用了nodemon,文件变更时会进行复制文件操作,触发了nodemon的重启导致浏览器reload时请求失败,所以解决办法是在nodemon配置文件中把复制文件的目录忽略掉,因为复制的真正内容是<style link='*****'></style><script src='*****'></script>这样的内容,所以完全是可以忽略的,忽略后文件变更时服务端也不会重启,问题解决。

5、vue devtools定位到组件目录

针对vue devtools提供的在浏览器选择组件时,点击时可以直接打开编译器并打开组件所在源代码文件,参考文档webpack.dev.js下新增下面配置

const openInEditor = require('launch-editor-middleware');

devServer: {
    ...
    before(app) {
        app.use('/__open-in-editor', openInEditor('code', path.resolve(process.cwd(), 'src')));
    },
},

whistle添加一行规则

https://www.midasbuy.com/__open-in-editor http://127.0.0.1:5000/__open-in-editor

如下图,点击圈出的该位置时会打开vscode编辑器并定位到组件对应源文件代码
361618191612_.pic_hd.jpg

6、总结

MacBook Pro Intel Core i7 16G内存250G硬盘大小测试环境下 对比不同方案下的编译时间和主要文件的gzip压缩前的大小结果如下,在处理大约65个文件情况下,可以看到编译时间大大缩短,js文件大小也有所减小,但是因为部分图片被base64编码打进css导致样式文件大小有所增加。

方案第一次编译时间第二次编译时间buypage.jspropsOrder.jsbuypage.csspropsOrder.css
gulp+webpack460s8s178kb157kb17.58kb24.74kb
webpack56.5s1.5s123kb112kb28kb35kb

timewilltell
68 声望4 粉丝

本人方向为前端,从八月份准备到九月份面试到十月份拿到满意的offer,前前后后大概两个月,期间面试了很多互联网公司,在面试中发现基础占了80%,项目和其他占了20%,现在面试结束了,想系统的把前端常见的面试问...