webpack4大结局:加入腾讯IM配置策略,实现前端工程化环境极致优化

2019-06-13 admin

图片描述

webpack,打包所有的资源

不知道不觉,webpack已经偷偷更新到4.34版本了,本人决定,这是今年最后一篇写webpack的文章,除非它更新到版本5,本人今年剩下的时间都会放在Golang和二进制数据操作以及后端的生态上

在看本文前,假设你对webpack有一定了解,如果不了解,可以看看我之前的手写ReactVue脚手架的文章

在此对webpack的性能优化进行几点声明:

  • 在部分极度复杂的环境下,需要双package.json文件,即实行三次打包
  • 在代码分割时,低于18K的文件没必要单独打包成一个chunk,http请求次数过多反而影响性能
  • prerenderPWA互斥,这个问题暂时没有解决
  • babel缓存编译缓存的是索引,即hash值,非常吃内存,每次开发完记得清理内存
  • babel-polyfill按需加载在某些非常复杂的场景下比较适合
  • prefetch,preload对首屏优化提升是明显
  • 代码分割不管什么技术栈,一定要做,不然就是垃圾项目
  • 多线程编译对构建速度提升也很明显
  • 代码分割配合PWA+预渲染+preload是首屏优化的巅峰,但是pwa无法缓存预渲染的html文件

本文的webpack主要针对React技术栈,实现功能如下:

  • 开发模式热更新
  • 识别JSX文件
  • 识别class组件
  • 代码混淆压缩,防止反编译代码,加密代码
  • 配置alias别名,简化import的长字段
  • 同构直出,SSR的热调试(基于Node做中间件)
  • 实现javaScripttree shaking 摇树优化 删除掉无用代码
  • 实现CSStree shaking
  • 识别 async / await 和 箭头函数
  • react-hot-loader记录react页面留存状态state
  • PWA功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用
  • preload 预加载资源 prefetch按需请求资源
  • CSS模块化,不怕命名冲突
  • 小图片的base64处理
  • 文件后缀省掉jsx js json
  • 实现React懒加载,按需加载 , 代码分割 并且支持服务端渲染
  • 支持less sass stylus等预处理
  • code spliting 优化首屏加载时间 不让一个文件体积过大
  • 加入dns-prefetchpreload预请求必要的资源,加快首屏渲染(京东策略)
  • 加入prerender,极大加快首屏渲染速度
  • 提取公共代码,打包成一个chunk
  • 每个chunk有对应的chunkhash,每个文件有对应的contenthash,方便浏览器区别缓存
  • 图片压缩
  • CSS压缩
  • 增加CSS前缀 兼容各种浏览器
  • 对于各种不同文件打包输出指定文件夹下
  • 缓存babel的编译结果,加快编译速度
  • 每个入口文件,对应一个chunk,打包出来后对应一个文件 也是code spliting
  • 删除HTML文件的注释等无用内容
  • 每次编译删除旧的打包代码
  • CSS文件单独抽取出来
  • 让babel不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度
  • 等等…

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

webpack打包原理

  • 识别入口文件
  • 通过逐层识别模块依赖。(Commonjs、amd或者es6的import,webpack都会对其进行分析。来获取代码的依赖)
  • webpack做的就是分析代码。转换代码,编译代码,输出代码
  • 最终形成打包后的代码
  • 这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。

舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要

  • 组件热刷新、CSS热刷新
  • 自从webpack推出热刷新后,前端开发者在开环境下体验大幅提高。
  • 没有热刷新能力,我们修改一个组件后

clipboard.png

clipboard.png

主要看一下React技术栈,如何在构建中接入热刷新

  • 无论什么技术栈,都需要在dev模式下加上 webpack.HotModuleReplacementPlugin插件
  devServer: {
        contentBase: '../build',
        open: true,
        port: 5000,
        hot: true
    },

注:也可以使用react-hot-loader来实现,具体参考官方文档

在开发模式下也要代码分割,加快打开页面速度

optimization: {
        runtimeChunk: true,
        splitChunks: {
            chunks: 'all',
            minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
            maxSize: 0,//没有限制
            minChunks: 3,// 共享最少的chunk数,使用次数超过这个值才会被提取
            maxAsyncRequests: 5,//最多的异步chunk数
            maxInitialRequests: 5,// 最多的同步chunks数
            automaticNameDelimiter: '~',// 多页面共用chunk命名分隔符
            name: true,
            cacheGroups: {// 声明的公共chunk
            vendor: {
            // 过滤需要打入的模块
            test: module => {
            if (module.resource) {
            const include = [/[\\/]node_modules[\\/]/].every(reg => {
            return reg.test(module.resource);
            });
            const exclude = [/[\\/]node_modules[\\/](react|redux|antd)/].some(reg => {
            return reg.test(module.resource);
            });
            return include && !exclude;
            }
            return false;
            },
            name: 'vendor',
            priority: 50,// 确定模块打入的优先级
            reuseExistingChunk: true,// 使用复用已经存在的模块
            },
            react: {
            test({ resource }) {
            return /[\\/]node_modules[\\/](react|redux)/.test(resource);
            },
            name: 'react',
            priority: 20,
            reuseExistingChunk: true,
            },
            antd: {
            test: /[\\/]node_modules[\\/]antd/,
            name: 'antd',
            priority: 15,
            reuseExistingChunk: true,
            },
            },
        }
    }

简要解释上面这段配置

  • 将node_modules共用部分打入vendor.js bundle中;
  • 将react全家桶打入react.js bundle中;
  • 如果项目依赖了antd,那么将antd打入单独的bundle中;(其实不用这样,可以看我下面的babel配置,性能更高)
  • 最后剩下的业务模块超过3次引用的公共模块,将自动提取公共块

注意 上面的配置只是为了给大家看,其实这样配置代码分割,性能更高

optimization: {
        runtimeChunk: true,
        splitChunks: {
            chunks: 'all',
                     }
}

react-hot-loader记录react页面留存状态state

yarn add react-hot-loader


// 在入口文件里这样写

import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";-------------------1、首先引入AppContainre
import { BrowserRouter } from "react-router-dom";
import Router from "./router";

/*初始化*/
renderWithHotReload(Router);-------------------2、初始化

/*热更新*/
if (module.hot) {-------------------3、热更新操作
  module.hot.accept("./router/index.js", () => {
    const Router = require("./router/index.js").default;
    renderWithHotReload(Router);
  });
}

function renderWithHotReload(Router) {-------------------4、定义渲染函数
  ReactDOM.render(
    <AppContainer>
      <BrowserRouter>
        <Router />
      </BrowserRouter>
    </AppContainer>,
    document.getElementById("app")
  );
}

然后你再刷新试试

React的按需加载,附带代码分割功能 ,每个按需加载的组件打包后都会被单独分割成一个文件


        import React from 'react'
        import loadable from 'react-loadable'
        import Loading from '../loading' 
        const LoadableComponent = loadable({
            loader: () => import('../Test/index.jsx'),
            loading: Loading,
        });
        class Assets extends React.Component {
            render() {
                return (
                    <div>
                        <div>这即将按需加载</div>
                        <LoadableComponent />
                    </div>
                )
            }
        }

        export default Assets

* 加入html-loader识别html文件

    {
    test: /\.(html)$/,
    loader: 'html-loader'
    }

配置别名

    resolve: {
    modules: [
    path.resolve(__dirname, 'src'), 
    path.resolve(__dirname,'node_modules'),
    ],
    alias: {
    components: path.resolve(__dirname, '/src/components'),
    },
    }

加入eslint-loader

    {
    enforce:'pre',
    test:/\.js$/,
    exclude:/node_modules/,
    include:resolve(__dirname,'/src/js'),
    loader:'eslint-loader'
    }

resolve解析配置,为了为了给所有文件后缀省掉 js jsx json,加入配置

resolve: {
    extensions: [".js", ".json", ".jsx"]
}

加入HTML文件压缩,自动将入门的js文件注入html中,优化HTML文件

 new HtmlWebpackPlugin({
            template: './public/index.html',
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
            }
        }),

SSR同构直出热调试

  • , 采用 webpack watch+nodemon 结合的模式实现对SSR热调试的支持。node 服务需要的html/js通过webpack插件动态输出,当nodemon检测到变化后将自动重启,html文件中的静态资源全部替换为dev模式下的资源,并保持socket连接自动更新页面。
  • 实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。下面是SSR热调试的流程图:

clipboard.png

加入 babel-loader 还有 解析JSX ES6语法的 babel preset

  • @babel/preset-react解析 jsx语法
  • @babel/preset-env解析es6语法
  • @babel/plugin-syntax-dynamic-import解析react-loadableimport按需加载,附带code spliting功能
  • ["import", { libraryName: "antd-mobile", style: true }], Antd-mobile的按需加载
{
                            loader: 'babel-loader',
                            options: {   //jsx语法
                                presets: ["@babel/preset-react",
                                    //tree shaking 按需加载babel-polifill
                                    ["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
                                plugins: [
                                    //支持import 懒加载 
                                    "@babel/plugin-syntax-dynamic-import",
                                    //andt-mobile按需加载  true是less,如果不用less style的值可以写'css' 
                                    ["import", { libraryName: "antd-mobile", style: true }],
                                    //识别class组件
                                    ["@babel/plugin-proposal-class-properties", { "loose": true }],
                                ],
                                cacheDirectory: true
                            },
                        }

特别提示,如果电脑性能不高,不建议开启babel缓存索引,非常吃内存,记得每次开发完了清理内存

加入thread-loader,在babel首次编译后开启多线程

    const os = require('os')
    {
            loader: 'thread-loader',
            options: {
                workers: os.cpus().length   
                     }
    }

加入单独抽取CSS文件的loader和插件

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

    {
        test: /\.(less)$/,
        use: [
            MiniCssExtractPlugin.loader,
            {
                loader: 'css-loader', options: {
                    modules: true,
                    localIdentName: '[local]--[hash:base64:5]'
                }
            },
            {loader:'postcss-loader'},
            { loader: 'less-loader' }
        ]
    }

     new MiniCssExtractPlugin({
            filename:'[name].[contenthash:8].css'
        }),

CSStree shaking

const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
    // 清除无用 css
    new PurifyCSS({
      paths: glob.sync([
        // 要做 CSS Tree Shaking 的路径文件
        path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html 文件进行 tree shaking
        path.resolve(__dirname, './src/*.js')
      ])
    })
]

对小图片进行base64处理,减少http请求数量,并对输出的文件统一打包处理

{
                    test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
                    loader: 'url-loader',
                    options: {
                        limit: 8 * 1024,
                        name: '[name].[hash:8].[ext]',

                    }
                }, {
                    exclude: /\.(js|json|less|css|jsx)$/,
                    loader: 'file-loader',
                    options: {
                        outputPath: 'media/',
                        name: '[name].[hash].[ext]'
                    }
                }
                ]
            }]
    },

加入单独抽取CSS文件的loader和插件

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

    {
        test: /\.(less)$/,
        use: [
            MiniCssExtractPlugin.loader,
            {
                loader: 'css-loader', options: {
                    modules: true,
                    localIdentName: '[local]--[hash:base64:5]'
                }
            },
            {loader:'postcss-loader'},
            { loader: 'less-loader' }
        ]
    }

     new MiniCssExtractPlugin({
            filename:'[name].[contenthash:8].css'
        }),

加入压缩css的插件

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    new OptimizeCssAssetsWebpackPlugin({
                cssProcessPluginOptions:{
                    preset:['default',{discardComments: {removeAll:true} }]
                }
            }),

加入每次打包输出文件清空上次打包文件的插件

    const CleanWebpackPlugin = require('clean-webpack-plugin')

    new CleanWebpackPlugin()

加入图片压缩

{
                test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,

                use:[
                    {loader: 'url-loader',
                    options: {
                        limit: 8 * 1024,
                        name: '[name].[hash:8].[ext]',
                        outputPath:'/img'
                    }},
                    {
                        loader: 'img-loader',
                        options: {
                          plugins: [
                            require('imagemin-gifsicle')({
                              interlaced: false
                            }),
                            require('imagemin-mozjpeg')({
                              progressive: true,
                              arithmetic: false
                            }),
                            require('imagemin-pngquant')({
                              floyd: 0.5,
                              speed: 2
                            }),
                            require('imagemin-svgo')({
                              plugins: [
                                { removeTitle: true },
                                { convertPathData: false }
                              ]
                            })
                          ]
                        }
                      }
                ]

            }

加入代码混淆,反编译

var JavaScriptObfuscator = require('webpack-obfuscator');

// ...

// webpack plugins array
plugins: [
    new JavaScriptObfuscator ({
      rotateUnicodeArray: true
  }, ['excluded_bundle_name.js'])
],

加入 PWA的插件 , WorkboxPlugin

  • pwa这个技术其实要想真正用好,还是需要下点功夫,它有它的生命周期,以及它在浏览器中热更新带来的副作用等,需要认真研究。可以参考百度的lavas框架发展历史~
const WorkboxPlugin = require('workbox-webpack-plugin')

    new WorkboxPlugin.GenerateSW({ 
                clientsClaim: true, //让浏览器立即servece worker被接管
                skipWaiting: true,  // 更新sw文件后,立即插队到最前面 
                importWorkboxFrom: 'local',
                include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
            }),

加入预渲染prerener

new PreloadWebpackPlugin({
            rel: 'preload',
            as(entry) {
                if (/\.css$/.test(entry)) return 'style';
                if (/\.woff$/.test(entry)) return 'font';
                if (/\.png$/.test(entry)) return 'image';
                return 'script';
            },
            include: 'allChunks'
            //include: ['app']
        }),

我这套webpack配置,无论多复杂的环境,都是可以搞定的

  • webpack真的非常非常重要,如果用不好,就永远是个初级前端
  • 只要webpack不更新到5,以后就不出webpack的文章了
  • webpack4大结局,谢谢
  • 以后会出一些偏向跨平台技术,原生javascriptTSGolang等内容的文章

[转载]原文链接:https://segmentfault.com/a/1190000019461556

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-67151.html

文章标题:webpack4大结局:加入腾讯IM配置策略,实现前端工程化环境极致优化

相关文章
从2014年的发展来展望JS的未来将会如何
&lt;font face=&quot;寰�杞�闆呴粦, Arial, sans-serif &quot;&gt;2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
破解前端面试(80% 应聘者不及格系列):从 闭包说起
不起眼的开始 招聘前端工程师,尤其是中高级前端工程师,扎实的 JS 基础绝对是必要条件,基础不扎实的工程师在面对前端开发中的各种问题时大概率会束手无策。在考察候选人 JS 基础的时候,我经常会提供下面这段代码,然后让候选人分析它实际运行的结...
2017-06-02
three.js实现围绕某物体旋转
话不多说,请看代码: 可以拖动右上角观察变化 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot; style=&quot;width: 100%; height:100%;&quot;&gt...
2017-02-17
前端交流QQ群
我们建立了一个前端交流QQ群供大家交流,有什么问题都可以在群里提问,欢迎你的加入,也希望我们大家能够在群里互帮互助,同时也能学到东西。 我们相信,前端有你更精彩! 为了让更多的小伙伴加入我们,欢迎大家转发扩散! 长按以上二维码加入我们 ...
2016-04-01
2014年最流行前端开发框架对比评测
如今,各种开发框架层出不穷,各有千秋。哪些是去年较受开发者关注的呢?前不久,云适配根据Github上的流行程度整理了2014年最受欢迎的6个前端开发框架,并进行对比说明,希望帮助有需要的朋友选择合适自己的前端框架。 1. Bootstrap...
2015-11-12
javaScript+turn.js实现图书翻页效果实例代码
为了实现图书翻页的效果我们在网上可以看到很多教程 在这里推荐turn.js 网上的turn.js 有api 不过是英文的  很多人看起来不方便 .关于代码也是奇形怪状在这里我将详细讲解如何使用turn.js实现翻页效果 ,本篇文章只是讲解 ...
2017-03-16
梳理前端开发使用eslint-prettier检查和格式化代码
问题痛点 在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 对于代码版本管理系统(svn 和 git或者其他)...
2018-05-07
js实现手机拍照上传功能
在前段时间的项目开发中,用到了拍照上传的地方,后来发现了最为简单的一种方法,现总结如下: &lt;form id=&quot;form&quot; method=&quot;post&quot; action=&quot;http:&#x2...
2017-03-06
如何为高负载网络优化Nginx 和 Node.js?
译者:AlfredCheung 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,...
2015-11-12
Web前端开发与iOS终端开发的异同
毕业之前一直在做前端开发,毕业后就转成做iOS开发,这两者有很多挺有意思的对比,尝试写下我能想到的它们的一些相同点和不同点。 语言 前端和终端作为面向用户端的程序,有个共同特点:需要依赖用户机器的运行环境,所以开发语言基本上是没有选择的,...
2016-01-13
回到顶部