webpack葵花宝典!基础配置大全解(中)

一. 性能优化介绍

开发环境性能优化

  • 优化打包构建速度
  • 优化代码调试

生产环境性能优化

  • 优化打包构建速度
  • 优化代码运行的性能

二. HMR

HMR: hot module replacement 热模块替换 / 模块热替换

作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块),极大提升构建速度。

  • 样式文件:可以使用HMR功能:因为style-loader内部实现了~
js 复制代码
devServer: {

    contentBase: resolve(__dirname, 'build'),

    compress: true,

    port: 3000,

    open: true,

    // 开启HMR功能

    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务

    hot: true

  }
  • js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码

注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。

js 复制代码
// index.js



import print from './print';

if (module.hot) {

  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效

  module.hot.accept('./print.js', function() {

    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。

    // 会执行后面的回调函数

    print();

  });

}
  • html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能)

解决:修改entry入口,将html文件引入

js 复制代码
entry: ['./src/js/index.js', './src/index.html'],

三. Source-map

source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)。

使用

js 复制代码
devtool: 'eval-source-map'
js 复制代码
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

种类

js 复制代码
source-map:外部

  错误代码准确信息 和 源代码的错误位置

inline-source-map:内联

  只生成一个内联source-map

  错误代码准确信息 和 源代码的错误位置

hidden-source-map:外部

  错误代码错误原因,但是没有错误位置

  不能追踪源代码错误,只能提示到构建后代码的错误位置

eval-source-map:内联

  每一个文件都生成对应的source-map,都在eval

  错误代码准确信息 和 源代码的错误位置

nosources-source-map:外部

  错误代码准确信息, 但是没有任何源代码信息

cheap-source-map:外部

  错误代码准确信息 和 源代码的错误位置 

  只能精确的行

cheap-module-source-map:外部

  错误代码准确信息 和 源代码的错误位置 

  module会将loader的source map加入

内联 和 外部的区别:

  1. 外部生成了文件,内联没有

  2. 内联构建速度更快

使用选择

开发环境:速度快,调试更友好

速度快(eval>inline>cheap>...)

js 复制代码
eval-cheap-souce-map

eval-source-map

调试更友好

js 复制代码
souce-map

cheap-module-souce-map

cheap-souce-map

--> eval-source-map / eval-cheap-module-souce-map

生产环境:源代码要不要隐藏? 调试要不要更友好

内联会让代码体积变大,所以在生产环境不用内联

js 复制代码
nosources-source-map 全部隐藏

hidden-source-map 只隐藏源代码,会提示构建后代码错误信息

--> source-map / cheap-module-souce-map

四. oneOf

oneOf可以使loader只匹配一次,避免重复执行。

使用oneOf数组将loader包裹起来,重复的要提取出来放到外面。

js 复制代码
  module: {

    rules: [

    // 在使用oneOf时与babel-loader重复,提取出来

      {

        test: /.js$/,

        exclude: /node_modules/,

        // 优先执行

        enforce: 'pre',

        loader: 'eslint-loader',

        options: {

          fix: true

        }

      },

      {

        // 以下loader只会匹配一个

        // 注意:不能有两个配置处理同一种类型文件

        oneOf: [

          {

            test: /.css$/,

            use: [...commonCssLoader]

          },

          {

            test: /.less$/,

            use: [...commonCssLoader, 'less-loader']

          },

          /*

            正常来讲,一个文件只能被一个loader处理。

            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:

              先执行eslint 在执行babel

          */

          {

            test: /.js$/,

            exclude: /node_modules/,

            loader: 'babel-loader',

            options: {

              presets: [

                [

                  '@babel/preset-env',

                  {

                    useBuiltIns: 'usage',

                    corejs: {version: 3},

                    targets: {

                      chrome: '60',

                      firefox: '50'

                    }

                  }

                ]

              ]

            }

          },

          {

            test: /.(jpg|png|gif)/,

            loader: 'url-loader',

            options: {

              limit: 8 * 1024,

              name: '[hash:10].[ext]',

              outputPath: 'imgs',

              esModule: false

            }

          },

          {

            test: /.html$/,

            loader: 'html-loader'

          },

          {

            exclude: /.(js|css|less|html|jpg|png|gif)/,

            loader: 'file-loader',

            options: {

              outputPath: 'media'

            }

          }

        ]

      }

    ]

  }

五. 缓存

babel缓存

js 复制代码
{

    test: /.js$/,

    exclude: /node_modules/,

    loader: 'babel-loader',

    options: {

      presets: [

        [

          '@babel/preset-env',

          {

            useBuiltIns: 'usage',

            corejs: { version: 3 },

            targets: {

              chrome: '60',

              firefox: '50'

            }

          }

        ]

      ],

      // 开启babel缓存

      // 第二次构建时,会读取之前的缓存

      cacheDirectory: true

    }

  },

让第二次打包构建速度更快

文件资源缓存

当在服务器端设置了文件的缓存过期时间,那么在此时间内都只会读取缓存的内容,不会向服务器请求资源。那么当上线后的项目更改内容后,在缓存时间内就无法更新内容。

  • hash: 每次wepack构建时会生成一个唯一的hash值。

问题: 因为js和css同时使用一个hash值。

如果重新打包,会导致所有缓存失效。(可是只改动一个文件,所有的文件缓存都失效了)

js 复制代码
// js文件

output: {

    filename: 'js/built.[hash:10].js',

    path: resolve(__dirname, 'build')

},

// css文件

new MiniCssExtractPlugin({

  filename: 'css/built.[hash:10].css'

}),
  • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

问题: js和css的hash值还是一样的

因为css是在js中被引入的,所以同属于一个chunk,所以缓存同时失效的问题依然存在。

js 复制代码
// js文件

output: {

    filename: 'js/built.[chunkhash:10].js',

    path: resolve(__dirname, 'build')

},

// css文件

new MiniCssExtractPlugin({

  filename: 'css/built.[chunkhash:10].css'

}),
  • contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样 (推荐)

解决了所有文件或者来源相同的文件缓存同时失效的问题。

js 复制代码
// js文件

output: {

    filename: 'js/built.[contenthash:10].js',

    path: resolve(__dirname, 'build')

},

// css文件

new MiniCssExtractPlugin({

  filename: 'css/built.[contenthash:10].css'

}),

作用:让代码上线运行缓存更好使用

js 复制代码
const { resolve } = require('path');

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

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

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



process.env.NODE_ENV = 'production';



// 复用loader

const commonCssLoader = [

  MiniCssExtractPlugin.loader,

  'css-loader',

  {

    // 还需要在package.json中定义browserslist

    loader: 'postcss-loader',

    options: {

      ident: 'postcss',

      plugins: () => [require('postcss-preset-env')()]

    }

  }

];



module.exports = {

  entry: './src/js/index.js',

  output: {

    filename: 'js/built.[contenthash:10].js',

    path: resolve(__dirname, 'build')

  },

  module: {

    rules: [

      {

        test: /.js$/,

        exclude: /node_modules/,

        enforce: 'pre',

        loader: 'eslint-loader',

        options: {

          fix: true

        }

      },

      {

        oneOf: [

          {

            test: /.css$/,

            use: [...commonCssLoader]

          },

          {

            test: /.less$/,

            use: [...commonCssLoader, 'less-loader']

          },

          {

            test: /.js$/,

            exclude: /node_modules/,

            loader: 'babel-loader',

            options: {

              presets: [

                [

                  '@babel/preset-env',

                  {

                    useBuiltIns: 'usage',

                    corejs: { version: 3 },

                    targets: {

                      chrome: '60',

                      firefox: '50'

                    }

                  }

                ]

              ],

              // 开启babel缓存

              // 第二次构建时,会读取之前的缓存

              cacheDirectory: true

            }

          },

          {

            test: /.(jpg|png|gif)/,

            loader: 'url-loader',

            options: {

              limit: 8 * 1024,

              name: '[hash:10].[ext]',

              outputPath: 'imgs',

              esModule: false

            }

          },

          {

            test: /.html$/,

            loader: 'html-loader'

          },

          {

            exclude: /.(js|css|less|html|jpg|png|gif)/,

            loader: 'file-loader',

            options: {

              outputPath: 'media'

            }

          }

        ]

      }

    ]

  },

  plugins: [

    new MiniCssExtractPlugin({

      filename: 'css/built.[contenthash:10].css'

    }),

    new OptimizeCssAssetsWebpackPlugin(),

    new HtmlWebpackPlugin({

      template: './src/index.html',

      minify: {

        collapseWhitespace: true,

        removeComments: true

      }

    })

  ],

  mode: 'production',

  devtool: 'source-map'

};

六. Tree shking

什么是tree shking

tree shaking首先是由rollup的作者提出的,它是DCE(dead code elimination)的一个实现,通过tree shaking的分析,可以使你代码里没有使用的代码全部删除。实际情况中,在 webpack 项目中,入口文件有很多依赖的模块,但是有时候虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。然而它又区别于普通的dec,这里作者有一个比喻很形象:

imagine that you made cakes by throwing whole eggs into the mixing bowl and smashing them up, instead of cracking them open and pouring the contents out. Once the cake comes out of the oven, you remove the fragments of eggshell, except that's quite tricky so most of the eggshell gets left in there.

简单翻译一下就是,如果将dec比作制作蛋糕,传统的dec做法就是,将整个鸡蛋丢进碗里搅拌,然后放进烤箱,烤完之后从做好的蛋糕里,找到不需要的蛋壳扔掉,而tree shaking是将鸡蛋打破把蛋黄等有用的东西丢进碗里搅拌,最后直接做出蛋糕。

为什么需要tree shaking

主要还是为了减少页面的加载时间,将无用的代码删除,减少js包的大小,从而减少用户等待的时间,使用户不因为漫长的等待而离开。

那为什么已经有了dec,还要做tree shaking呢,根据作者的意思是,由于js静态语法分析的局限性,从已有代码里去删除代码不如去寻找真正使用的代码来的好。

作用:去除无用代码,减少代码体积。

开启前提

前提:1. 必须使用ES6模块化 2. 开启production环境

js 复制代码
mode: 'production';

在package.json中配置

js 复制代码
"sideEffects": false 

所有代码都没有副作用(都可以进行tree shaking)

问题:可能会把css / @babel/polyfill (副作用)文件干掉

例如:避免css文件被删除掉

js 复制代码
"sideEffects": ["*.css", "*.less"]

七. Code split

方式一(配置optimization方式)

单文件使用optimization 打包只能node_modules等第三方库中代码单独打包一个chunk最终输出。

问题:无法单独打包自定义的js文件。

多入口文件使用optimization 打包可以将自定义js代码文件也单独打包。(多入口使用较少)

自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk。

js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

const { resolve } = require('path');



module.exports = {

    // 单入口

    // entry: './src/index',

    // 多入口

    entry: {

        index: './src/index',

        test: './src/test'

    },

    output: {

        // 输出文件名称需要改为动态 [name]:取文件名

        filename: 'js/[name].[contenthash:10].js',

        path: resolve(__dirname, 'build')

    },

    module: {



    },

    plugins:[

        new HtmlWebpackPlugin({

            template: './src/index.html'

        })

    ],



    optimization: { 

 splitChunks: { 

 chunks: 'all' 

 } 

 }, 

    mode: 'production'

}

方式二(在js代码中使用import方式)

js 复制代码
// 单入口

entry: './src/index',

在配置optimization 的基础上,在js文件引入中:

js 复制代码
 // 通过js代码,让某个文件被单独打包成一个chunk

 // import动态导入语法:能将某个文件单独打包

 // /* webpackChunkName: 'test' */ 为更改文件名

import(/* webpackChunkName: 'test' */'./test')

.then(({num})=>{

    // 文件加载成功!

    console.log(num(4,2))

})

.catch(()=>{

    console.log('文件加载失败')

})

打包文件:

文件打包后目录:

八. 预加载与懒加载

正常加载可以认为是并行加载(同一时间加载多个文件) 。

懒加载:当文件需要使用时才加载。

缺点:当加载文件较大时,用户触发后再加载可能会有卡顿

js 复制代码
// 懒加载

// webpackChunkName: 'test'更改打包后文件名

// import { mul } from './test';



// 点击按钮后才会加载test文件

document.getElementById('btn').onclick = function() {

  import(/* webpackChunkName: 'test' */'./test').then(({ mul }) => {

    console.log(mul(4, 5));

  });

};

预加载 prefetch:会在使用之前,提前加载js文件 ,等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。

缺点:兼容性

js 复制代码
// 预加载

// webpackPrefetch: true



document.getElementById('btn').onclick = function() {

  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {

    console.log(mul(4, 5));

  });

};

九. PWA

PWA: 渐进式网络开发应用程序(离线可访问)

  1. 安装插件
js 复制代码
npm i workbox-webpack-plugin -D
  1. 在webpack.config.js中配置
js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')

const { resolve } = require('path');



module.exports = {

    entry: './src/index.js',

    output: {

        filename: 'built.js',

        path: resolve(__dirname, 'build')

    },



    module: {

        rules: []

    },

    plugins:[

        new HtmlWebpackPlugin({

            template: './src/index.html'

        }),

        // 引入 WorkboxWebpackPlugin

        new WorkboxWebpackPlugin.GenerateSW({

            /*

            1. 帮助serviceworker快速启动

            2. 删除旧的 serviceworker

    

            生成一个 serviceworker 配置文件~

          */

            clientsClaim: true,

            skipWaiting: true

        })

    ],

    mode: 'production'

}
  1. 在入口文件中注册
js 复制代码
// 注册serviceWorker

// 处理兼容性问题

if ('serviceWorker' in navigator) {

  window.addEventListener('load', () => {

    navigator.serviceWorker

      .register('/service-worker.js')

      .then(() => {

        console.log('sw注册成功了~');

      })

      .catch(() => {

        console.log('sw注册失败了~');

      });

  });

}

问题一

eslint不认识 window、navigator全局变量(实际demo中并没有碰到)

解决:需要修改package.json中eslintConfig配置

js 复制代码
  "env": {

    "browser": true // 支持浏览器端全局变量

  }

问题二

sw代码必须运行在服务器上

处理方式一:使用nodejs搭建服务器

处理方式二:

js 复制代码
// 使用serve包自动搭建服务器

npm i serve -g



// 启动服务器,将build目录下所有资源作为静态资源暴露出去 

// build为打包后文件名

serve -s build 

此时请求的文件资源为service worker中:

十. 多进程打包

  1. 安装插件
js 复制代码
npm i thread-loader -D
  1. 使用
js 复制代码
{

    test: /.js$/,

    exclude: /node_modules/,

    use: [

      /* 

        开启多进程打包。 

        进程启动大概为600ms,进程通信也有开销。

        只有工作消耗时间比较长,才需要多进程打包

      */

      // 对babel进行多进程打包

      {

        loader: 'thread-loader',

        options: {

           // 配置两个进程

          workers: 2 

        }

      },

      {

        loader: 'babel-loader',

        options: {

          presets: [

            [

              '@babel/preset-env',

              {

                useBuiltIns: 'usage',

                corejs: { version: 3 },

                targets: {

                  chrome: '60',

                  firefox: '50'

                }

              }

            ]

          ],

          cacheDirectory: true

        }

      }

    ]

  }

只有工作消耗时间比较长,才需要多进程打包,耗时较短的任务开启多进程打包后会比之前所用的时间更长。

十一. externals

排除不想被打包的文件

js 复制代码
const { resolve } = require('path');

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



module.exports = {

  entry: './src/js/index.js',

  output: {

    filename: 'js/built.js',

    path: resolve(__dirname, 'build')

  },

  plugins: [

    new HtmlWebpackPlugin({

      template: './src/index.html'

    })

  ],

  mode: 'production',

  

  // 排除jquery,避免第三方包被打包

  externals: {

    // 拒绝jQuery被打包进来

    jquery: 'jQuery'

  }

};

但是要在index.html中指定jquery的远程cdn链接

js 复制代码
<!DOCTYPE html>

<html lang="en">



<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>webpack</title>

</head>



<body>

  <h1 id="title">hello html</h1>

  // 引入cdn链接

  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

</body>



</html>

十二. Dll

使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包

当你运行 webpack 时,默认查找 webpack.config.js 配置文件。

  1. 创建webpack.dll.js文件(文件名可以随意)
js 复制代码
/*

  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包

    当你运行 webpack 时,默认查找 webpack.config.js 配置文件

    需求:需要运行 webpack.dll.js 文件

      --> webpack --config webpack.dll.js

*/



const { resolve } = require('path');

const webpack = require('webpack');



module.exports = {

  entry: {

    // 最终打包生成的[name] --> jquery

    // ['jquery'] --> 要打包的库是jquery

    jquery: ['jquery'],

  },

  

  // 生成jquery文件

  output: {

    filename: '[name].js',

    path: resolve(__dirname, 'dll'),

    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字

  },

  

  // 生成json与jquery的映射文件

  plugins: [

    // 打包生成一个 manifest.json --> 提供和jquery映射

    new webpack.DllPlugin({

      name: '[name]_[hash]', // 映射库的暴露的内容名称

      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径

    })

  ],

  mode: 'production'

};
  1. 在项目目录下运行:
js 复制代码
webpack --config webpack.dll.js

会生成一个dll文件,此文件为独立生成的jquery文件和json映射文件

  1. 安装插件
js 复制代码
npm i add-asset-html-webpack-plugin -D
  1. webpack.config.js中的文件配置
js 复制代码
const { resolve } = require('path');

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

const webpack = require('webpack');

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');



module.exports = {

  entry: './src/index.js',

  output: {

    filename: 'built.js',

    path: resolve(__dirname, 'build')

  },

  plugins: [

    new HtmlWebpackPlugin({

      template: './src/index.html'

    }),

    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~

    // 使第三方包不参与打包的配置

    new webpack.DllReferencePlugin({

      manifest: resolve(__dirname, 'dll/manifest.json')

    }),

    // 将某个文件打包输出去,并在html中自动引入该资源

    // 自动引入执行webpack.dll.js文件生成的目录文件

    new AddAssetHtmlWebpackPlugin({

      filepath: resolve(__dirname, 'dll/jquery.js'),

      publicPath: ''

    })

  ],

  mode: 'production'

};

重新执行webpack打包后,无需手动引入jquery文件,在压缩后的index.html文件中会自动引入。

相关推荐
csdnLN16 分钟前
$.ajax() 对应事件done() 、fail()、always() 的用法
前端·javascript·ajax
甜味橘阳17 分钟前
echarts地图可视化展示
前端·javascript·echarts
bloxed1 小时前
前端文件下载多方式集合
前端·filedownload
余生H1 小时前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC1 小时前
CSS(四)display和float
前端·css
cwtlw1 小时前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
米奇妙妙wuu2 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖2 小时前
React 生命周期完整指南
前端·react.js
梦境之冢2 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http