webpack 性能优化

191 阅读4分钟

image.png

基本配置

1、拆分配置和merge ?

安装 webpack-merge

image.png

把公共的文件放在webpack.common.js中;webpack.dev.js是生产环境的配置;webpack.prod.js是开发环境的配置;

在开发环境和生产环境中分别引入公共配置,使用 smart 进行合并:

webpack.prod.js

const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    ...
}

webpack.dev.js

const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    ...
}

2、启动本地服务

安装 webpack-dev-server

packget.json的scripts中配置命令: image.png

webpack.dev.js中配置 devServer:

devServer: {
    port: 8080,
    progress: true,  // 显示打包的进度条
    contentBase: distPath,  // 根目录
    open: true,  // 自动打开浏览器
    compress: true,  // 启动 gzip 压缩

    // 设置代理
    proxy: {
        // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
        '/api': 'http://localhost:3000',

        // 将本地 /api2/xxx 代理到 localhost:3000/xxx
        '/api2': {
            target: 'http://localhost:3000',
            pathRewrite: {
                '/api2': ''
            }
        }
    }
}

3、处理 ES6

webpack.common.js中配置:

module: {
    rules: [
        {
            test: /.js$/,
            loader: ['babel-loader'],
            include: srcPath,  // 包含的文件路径
            exclude: /node_modules/  // 不包含的路径
        },
        .....
     ]
 }

4、处理样式

安装postcss-loader 添加浏览器兼容性;

安装autoprefixer 适用于普通的CSS,可以实现css3代码自动补全。也可以轻松跟Sass,LESS及Stylus集成,在CSS编译前或编译后运行,会清理过期的前缀,因此下面的代码:

css 代码:

a {
-webkit-border-radius : 5px;
border-radius : 5px
}

编译成: css 代码:

a {
border-radius : 5px
}

因为经过autoprefixerr处理,CSS将仅包含实际的浏览器前缀。

配置:

1、在根目录上新建一个文件,postcss.config.js,配置autoprefixer,才会生效:

module.exports = {
    plugins: [require('autoprefixer')]
}

2、在 webpack.common.js中配置,

// {
//     test: /.css$/,
//     // loader 的执行顺序是:从后往前(知识点)
//     loader: ['style-loader', 'css-loader']
// },
{
    test: /.css$/,
    // loader 的执行顺序是:从后往前
    loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
  
{
    test: /.less$/,
    // 增加 'less-loader' ,注意顺序
    loader: ['style-loader', 'css-loader', 'less-loader']
}
},

5、处理图片

webpack.prod.js文件中配置:

···
rules: [
    // 图片 - 考虑 base64 编码的情况
    {
        test: /.(png|jpg|jpeg|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                // 小于 5kb 的图片用 base64 格式产出
                // 否则,依然延用 file-loader 的形式,产出 url 格式
                limit: 5 * 1024,

                // 打包到 img 目录下
                outputPath: '/img1/',

                // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                // publicPath: 'http://cdn.abc.com'
            }
        }
    },
    ...
]
...

6、模块化

webpack 默认支持,用的时候再配置

7、基本配置代码

webpack.common.js 代码:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
    entry: path.join(srcPath, 'index'),
    module: {
        rules: [
            {
                test: /.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/
            },
            // {
            //     test: /.vue$/,
            //     loader: ['vue-loader'],
            //     include: srcPath
            // },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前(知识点)
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]
}

webpack.dev.js 代码:

const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')

module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            // 直接引入图片 url
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'development'
            ENV: JSON.stringify('development')
        })
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩

        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',

            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    }
})

webpack.prod.js 代码:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')

module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,

                        // 打包到 img 目录下
                        outputPath: '/img1/',

                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        })
    ]
})

高级配置

1、多入口

index.htmlother.html 两个入口文件如何配置:

首先安装html-webpack-plugin插件,然后配置webpack.common.js文件:

const HtmlWebpackPlugin = require('html-webpack-plugin')

entry: {
    index: path.join(srcPath, 'index.js'),
    other: path.join(srcPath, 'other.js')
},

....

plugins: [
      ...
    // new HtmlWebpackPlugin({
    //     template: path.join(srcPath, 'index.html'),
    //     filename: 'index.html'
    // })

    // 多入口 - 生成 index.html
    new HtmlWebpackPlugin({
        template: path.join(srcPath, 'index.html'),
        filename: 'index.html',
        // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
        chunks: ['index']  // 只引用 index.js
    }),
    // 多入口 - 生成 other.html
    new HtmlWebpackPlugin({
        template: path.join(srcPath, 'other.html'),
        filename: 'other.html',
        chunks: ['other']  // 只引用 other.js
    })
    ...
]

接着在 webpack.prod.js 文件中配置:

output: {
    // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
    filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
    path: distPath,
    // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},

...
plugins: [
    new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
   ...
]

2、抽离 css 文件

安装 mini-css-extract-plugin 插件

webpack.prod.js 文件

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')

module: {
    rules: [
        ... 
        // 抽离 css
        {
            test: /.css$/,
            loader: [
                MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-loader
                'css-loader',
                'postcss-loader'
            ]
        },
        // 抽离 less --> css
        {
            test: /.less$/,
            loader: [
                MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-loader
                'css-loader',
                'less-loader',
                'postcss-loader'
            ]
        }
    ]
},

plugins: [
   ...

    // 抽离 css 文件
    new MiniCssExtractPlugin({
        filename: 'css/main.[contentHash:8].css'
    })
    ...
],

optimization: {
    // 压缩 css
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}

3、抽离公共代码

webpack.prod.js文件中:

optimization: {
   
   ....

    // 分割代码块
    splitChunks: {
        chunks: 'all',
        /**
         * initial 入口 chunk,对于异步导入的文件不处理
            async 异步 chunk,只对异步导入的文件处理
            all 全部 chunk
         */

        // 缓存分组
        cacheGroups: {
            // 第三方模块
            vendor: {
                name: 'vendor', // chunk 名称
                priority: 1, // 权限更高,优先抽离,重要!!!
                test: /node_modules/,
                minSize: 0,  // 大小限制
                minChunks: 1  // 最少复用过几次
            },

            // 公共的模块
            common: {
                name: 'common', // chunk 名称
                priority: 0, // 优先级
                minSize: 0,  // 公共模块的大小限制
                minChunks: 2  // 公共模块最少复用过几次
            }
        }
    }
}

4、懒加载

5、处理 JSX

首先安装 @babel/preset-react

然后在 .babelrc 文件中进行处理:

image.png

6、处理Vue

安装 vue-loader

7、module、chunk、bundle 的区别?

image.png image.png

性能优化-优化构建速度

1、优化babel-loader

image.png

2、IgnorePlugin

避免引用无用模块

image.png

3、noParse

避免重复打包

image.png

image.png

4、happyPack

image.png

安装 happypack

修改 webpack.prod.js 配置

const HappyPack = require('happypack')

module: {
    rules: [
        // js
        {
            test: /.js$/,
            // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
            use: ['happypack/loader?id=babel'],
            include: srcPath,
            // exclude: /node_modules/
        },
        ...
    ]
 }
 ...
 
plugins: [
   ...
    // happyPack 开启多进程打包
    new HappyPack({
        // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
        id: 'babel',
        // 如何处理 .js 文件,用法和 Loader 配置中一样
        loaders: ['babel-loader?cacheDirectory']
    }),
    ...
  ]
 

5、ParalleIUglifyPlugin

多进程压缩 JS

image.png

安装插件 webpack-parallel-uglify-plugin

webpack.prod.js 文件中配置:

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

plugins: [
...
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
    // 传递给 UglifyJS 的参数
    // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
    uglifyJS: {
        output: {
            beautify: false, // 最紧凑的输出
            comments: false, // 删除所有的注释
        },
        compress: {
            // 删除所有的 `console` 语句,可以兼容ie浏览器
            drop_console: true,
            // 内嵌定义了但是只用到一次的变量
            collapse_vars: true,
            // 提取出出现多次但是没有定义成变量去引用的静态值
            reduce_vars: true,
        }
    }
})
...
]

关于多进程打包的选择:

image.png

6、自动刷新

image.png

7、热更新

image.png

安装插件:

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
...
entry: {
    // index: path.join(srcPath, 'index.js'),
    index: [
        'webpack-dev-server/client?http://localhost:8080/',
        'webpack/hot/dev-server',
        path.join(srcPath, 'index.js')
    ],
    other: path.join(srcPath, 'other.js')
},

plugins: [
    ...
    new HotModuleReplacementPlugin()
],

devServer: {
   ...
   
    hot: true,
  }

image.png

8、DllPlugin

动态链接库插件

image.png

image.png

创建webpack.dll.js 文件

const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
  mode: 'development',
  // JS 执行入口文件
  entry: {
    // 把 React 相关模块的放到一个单独的动态链接库
    react: ['react', 'react-dom']
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
    // 也就是 entry 中配置的 react 和 polyfill
    filename: '[name].dll.js',
    // 输出的文件都放到 dist 目录下
    path: distPath,
    // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
    library: '_dll_[name]',
  },
  plugins: [
    // 接入 DllPlugin
    new DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      // 例如 react.manifest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(distPath, '[name].manifest.json'),
    }),
  ],
}

在 package.json文件中进行配置:

"scripts": {
  ...
  "dll": "webpack --config build/webpack.dll.js"
},

执行 npm run dll 生成dist目录

image.png

index.html 中引入文件react.dll.js

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>

    <script src="./react.dll.js"></script>
</body>
</html>

webpack.dev.js 中进行配置:


// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            {
                test: /.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
            },
        ]
    },
    plugins: [
       ...
        // 第三,告诉 Webpack 使用了哪些动态链接库
        new DllReferencePlugin({
            // 描述 react 动态链接库的文件内容
            manifest: require(path.join(distPath, 'react.manifest.json')),
        }),
    ],
    devServer: {
        ...
    }
})

9、总结

image.png

image.png

性能优化-优化产出代码

image.png

1、优化方案

image.png

image.png

2、production

image.png

ES6 Module 和 CommonJS 的区别?

image.png

image.png

3、Scope Hosting

image.png

image.png

image.png

image.png

配置:

image.png

构建流程概述

babel

image.png

1、环境搭建 && 基本配置

image.png

image.png

2、babel-polyfill

image.png

image.png

image.png

.babelrc文件

"presets": [
    [
        "@babel/preset-env",
        {
            "useBuiltIns": "usage",
            "corejs": 3
        }
    ]
],

image.png

3、babel-runtime

.babelrc文件

"plugins": [
    [
        "@babel/plugin-transform-runtime",
        {
            "absoluteRuntime": false,
            "corejs": 3,
            "helpers": true,
            "regenerator": true,
            "useESModules": false
        }
    ]
]

面试题

1、前端为何需要打包和构建 ?

image.png

image.png

2、module、chunk和bundle的区别 ?

image.png

3、loader 和 plugin 的区别 ?

image.png

4、常见的 loader 和 plugin 有哪些 ?

www.webpackjs.com/loaders/ www.webpackjs.com/plugins/

5、babel 和 webpack 的区别 ?

image.png

6、如何产出 lib ?

image.png

7、babel-polyfile 和 babel-runtime 的区别?

image.png

8、webpack 如何实现懒加载 ?

image.png

9、为何 proxy 不能被 Polyfill ?

image.png

10、webpack构建速度?

image.png

image.png

image.png

image.png