webpack 常见问题及优化

Loaders

1、文件Loader

url-loaderfile loader 一样工作,可以返回 data URL

{
     test: /\.(jpg|png|gif|jpeg)$/,
     loader: 'url-loader',
     options:{
        name: 'img/[name]-[hash:8].[ext]',
        outputPath: 'img',
        esModule: false
     }
}

file-loader 将文件发送到输出文件夹,并返回相对URL

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

2、转换编译(Transpiling)

Name Description
script-loader 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
ts-loader awesome-typescript-loaderJavaScript 一样加载 TypeScript 2.0+
coffee-loader JavaScript一样加载CoffeeScript`

3、模板(Templating)

Name Description
html-loader 导出 HTML 为字符串,需要引用静态资源
pug-loader 加载 Pug 模板并返回一个函数
jade-loader 加载 Jade 模板并返回一个函数
markdown-loader Markdown 转译为 HTML

4、样式

  • style-loader 将模块的导出作为样式添加到 DOM
  • css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
  • less-loader 加载和转译 LESS 文件
  • sass-loader 加载和转译 SASS/SCSS 文件
  • postcss-loader 使用 PostCSS 加载和转译
 yarn add postcss-loader postcss-preset-env
  • stylus-loader 加载和转译 Stylus 文件

5、清理和测试(Linting)

eslint-loader 使用 ESLint语法检查 设置代码检查的规则:

在package.json里面添加:

eslintConfig:{
      extends: 'airbns'
}

添加依赖文件

yarn add eslint-config-airbnb-base  eslint-plugin-import  eslint -D

webpack.config.js 文件中

{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'eslint-loader',
      options: {
            fix: true //自动格式化代码
      }
}

eslint-loader 使用 ESLint兼容性检查

1、 preset-env 只能检查简单的语法。
2、@babel/polyfull 能解决兼用性问题。但是全部引入体积太大了。
3、 core-js第三方按需加载的包

添加依赖文件

npm install babel-loader @babel/core @bebel/preset-env -D 

webpack.config.js文件中

{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      options: {
            presets: [
                  @babel/preset-env,
                  {
                        useBuiltIns: 'usage',//按需加载
                        corejs: {
                              versions: 3//指定版本
                        },
                        targets: {
                              chrome: 60 //指定浏览器的版本
                        }
                  }
            ]
      }
}

Plugins

1、HtmlWebpackPlugin 简单创建 HTML 文件

 plugins: [
      new HtmlWebpackPlugin({
            title: '',
            filename: '',
            template: './src/index.html',
            publicPath: '',
            favicon: '',
            neta: {}
      })
]

压缩HTML文件

plugins:[
      new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                  collapseWhitespace: true,
                  removeComments: true
            }
      })
]

2、MinCssExtractPlugin 单独提取css文件

plugins: [
      new MinCssExtractPlugin({
            filename: 'css/style.css',
            chunkFilename: ''
      })
]

配合loader,postcss-loader 做浏览器的兼容性。添加了postcss-loader 后,要在package.json 里面配置browserslist:{development: {}, production: {}}对象。

{
      test: /\.css$/,
      use: [
            //style-loader
            MinCssExtractPlugin.laoder,
            css-loader,
            {
                  loader: 'postcss-loader',
                  options: {
                        ident: 'postcss',
                        plugins: () => require('postcss-preset-env')()
                  }
            }
      ]
}

3、OptimizeCssAssetsWebpackPlugin 压缩css

plugins:[
      new OptimizeCssAssetsWebpackPlugin()
]

一些小的优化点

resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面。
resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径。

module.exports ={
    // ...省略其他配置
    resolve: {
        extensions: [".js",".jsx",".json",".css"],
        alias:{
            "jquery":jquery
        }
    }
};

提高 Webpack 打包速度

1、优化Loader搜索范围(include,exclude

合理的使用exclude或者include的配置,来尽量减少loader被频繁执行的频率。当loader执行频率降低时,也会提升webpack的打包速度。比如:
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,我们是有办法优化的。
首先我们可以优化 Loader 的文件搜索范围,在使用loader时,我们可以指定哪些文件不通过loader处理,或者指定哪些文件通过loader处理。

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        use: ['babel-loader'],
        // 只处理src文件夹下面的文件
        include: path.resolve('src'),
        // 不处理node_modules下面的文件
        exclude: /node_modules/
      }
    ]
  }
}

2、cache-loader缓存loader处理结果

缓存分为 babel缓存和文件资源缓存

Babel 缓存 cacheDirectory: true

{
      cacheDirectory: true
}

文件资源的缓存

1.hash 每次webpack构建时都会生成一个唯一的hash值 问题: 应为js和css同时使用一个hash值,如果重新打包就会导致缓存失效。
2.chunkhash 根据chunk生产的hash值,如果打包来源于同一个chunk,那么同一个文件的js和css的hash值就时一样的。
3.contenthash 根据文件的内容生成hash值,不同文件的hash值是不一样的。

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        use: [
          'cache-loader',
          ...loaders
        ],
      }
    ]
  }
}

3、使用多线程处理打包

module: {
  rules: [
    {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules'),
    },
    {
        test: /\.css$/,
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        use: ['happypack/loader?id=css']
    }
  ]
},
plugins: [
  	new HappyPack({
        id: 'js', //ID是标识符的意思,ID用来代理当前的happypack是用来处理一类特定的文件的
        threads: 4, //你要开启多少个子进程去处理这一类型的文件
        loaders: [ 'babel-loader' ]
    }),
    new HappyPack({
        id: 'css',
        threads: 2,
        loaders: [ 'style-loader', 'css-loader' ]
    })
]

4、DllPlugin&DllReferencePlugin 成动态链接库

DllPlugin可以将特定的类库提前打包成动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据,把基础模块独立出来打包到单独的动态连接库里,当需要导入的模块在动态连接库里的时候,模块不用再次被打包,而是去动态连接库里获取。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。
这里我们可以先将react、react-dom单独打包成动态链接库,首先新建一个新的webpack配置文件:webpack.dll.js

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
	// 想统一打包的类库
    entry:['react','react-dom'],
    output:{
        filename: '[name].dll.js',  //输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称
        path:path.resolve(__dirname,'dll'),  // 输出的文件都放到 dll 目录下
        library: '_dll_[name]',//存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    },
    plugins:[
        new DllPlugin({
            // 动态链接库的全局变量名称,需要和 output.library 中保持一致
            // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
            // 例如 react.manifest.json 中就有 "name": "_dll_react"
            name: '_dll_[name]',
            // 描述动态链接库的 manifest.json 文件输出时的文件名称
            path: path.join(__dirname, 'dll', '[name].manifest.json')
        })
    ]
}

然后我们需要执行这个配置文件生成依赖文件:

webpack --config webpack.dll.js --mode development

接下来我们需要使用 DllReferencePlugin 将依赖文件引入项目中

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
  // ...省略其他配置
  plugins: [
    new DllReferencePlugin({
      // manifest 就是之前打包出来的 json 文件
      manifest:path.join(__dirname, 'dll', 'react.manifest.json')
    })
  ]
}

5、noParse 可以用于配置那些模块文件的内容不需要进行解析(即无依赖)

module.noParse 属性,可以用于配置那些模块文件的内容不需要进行解析(即无依赖) 的第三方大型类库(例如jquery,lodash)等,使用该属性让 Webpack不扫描该文件,以提高整体的构建速度。

module.exports = {
    module: {
      noParse: /jquery|lodash/, // 正则表达式
      // 或者使用函数
      noParse(content) {
        return /jquery|lodash/.test(content)
      }
    }
}

6、IgnorePlugin IgnorePlugin用于忽略某些特定的模块

IgnorePlugin用于忽略某些特定的模块,让webpack 不把这些指定的模块打包进去。

module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.IgnorePlugin(/^\.\/locale/,/moment$/)
  ]
}

webpack.IgnorePlugin()参数中第一个参数是匹配引入模块路径的正则表达式,第二个参数是匹配模块的对应上下文,即所在目录名。

7、打包文件分析工具

webpack-bundle-analyzer插件的功能是可以生成代码分析报告,帮助提升代码质量和网站性能。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
      plugins: [
          new BundleAnalyzerPlugin({
            generateStatsFile: true, // 是否生成stats.json文件
          })  
        // 默认配置的具体配置项
        // new BundleAnalyzerPlugin({
        //   analyzerMode: 'server',
        //   analyzerHost: '127.0.0.1',
        //   analyzerPort: '8888',
        //   reportFilename: 'report.html',
        //   defaultSizes: 'parsed',
        //   openAnalyzer: true,
        //   generateStatsFile: false,
        //   statsFilename: 'stats.json', 
        //   statsOptions: null,
        //   excludeAssets: null,
        //   logLevel: info
        // })
  ]
}

使用方式:

"generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件
"analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展示打包报告的http服务器

8、费时分析

speed-measure-webpack-plugin,打包速度测量插件。这个插件可以测量webpack构建速度,可以测量打包过程中每一步所消耗的时间,然后让我们可以有针对的去优化代码。

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
// 用smw.wrap()包裹webpack的所有配置项
module.exports =smw.wrap({
    module: {},
    plugins: []
});

减少 Webpack 打包后的文件体积

1、对图片进行压缩和优化

image-webpack-loader这个loder可以帮助我们对打包后的图片进行压缩和优化,例如降低图片分辨率,压缩图片体积等。

module.exports ={
    // ...省略其他配置
    module: {
        rules: [
            {
                test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
                use: [
                    'file-loader',
                    {
                        loader: 'image-webpack-loader',
                        options: {
                            mozjpeg: {
                                progressive: true,
                                quality: 65
                            },
                            optipng: {
                                enabled: false,
                            },
                            pngquant: {
                                quality: '65-90',
                                speed: 4
                            },
                            gifsicle: {
                                interlaced: false,
                            },
                            webp: {
                                quality: 75
                            }
                        }
                    }
                ]
            }
        ]
    }
};

2、删除无用的CSS样式

有时候一些时间久远的项目,可能会存在一些CSS样式被迭代废弃,需要将其剔除掉,此时就可以使用purgecss-webpack-plugin插件,该插件可以去除未使用的CSS,一般与 glob、glob-all 配合使用。
注意:此插件必须和CSS代码抽离插件mini-css-extract-plugin配合使用。
例如我们有样式文件style.css:

body{
    background: red
}
.class1{
    background: red
}

这里的.class1显然是无用的,我们可以搜索src目录下的文件,删除无用的样式。

const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

module.exports ={
    // ...
    plugins: [
        // 需要配合mini-css-extract-plugin插件
        new PurgecssPlugin({
            paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, 
                  {nodir: true}), // 不匹配目录,只匹配文件
            })
        }),
    ]
}

3、以CDN方式加载资源

我们知道,一般常用的类库都会发布在CDN上,因此,我们可以在项目中以CDN的方式加载资源,这样我们就不用对资源进行打包,可以大大减少打包后的文件体积。
以CDN方式加载资源需要使用到add-asset-html-cdn-webpack-plugin插件。我们以CDN方式加载jquery为例:

const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin')

module.exports ={
    // ...
    plugins: [
        new AddAssetHtmlCdnPlugin(true,{
            'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'
        })
    ],
    //在配置文件中标注jquery是外部的,这样打包时就不会将jquery进行打包了
    externals:{
      'jquery':'$'
    }
}

4、开启Tree Shaking (去除无用的代码,减少代码的体积。前提条件: 1.模块必须是ES6模块,2.必须是production环境)

Tree-shaking,摇晃树。顾名思义就是当我们摇晃树的时候,树上干枯的没用的叶子就会掉下来。类比到我们的代码中就是将没用的代码摇晃下来,从而实现删除代码中未被引用的代码。
这个功能在webpack4中,当我们将mode设置为production时,会自动进行tree-shaking

在package.json 种配置 sideEffects: false 所有的代码都可以被tree-shaking 问题是: 可能吧css文件和 @babel/polyfill 等副作用全部干掉。要小心使用。
解决方法 {sideEffects: ['*.css']}

5、按需加载&动态加载

必大家在开发单页面应用项目的时候,项目中都会存在十几甚至更多的路由页面。如果我们将这些页面全部打包进一个文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,我们肯定是希望首页能加载的文件体积越小越好,这时候我们就可以使用按需加载,将每个路由页面单独打包为一个文件。在给单页应用做按需加载优化时,一般采用以下原则:

  • 对网站功能进行划分,每一类一个chunk
  • 对于首次打开页面需要的功能直接加载,尽快展示给用户,某些依赖大量代码的功能点可以按需加载
  • 被分割出去的代码需要一个按需加载的时机
    动态加载目前并没有原生支持,需要babel的插件:plugin-syntax-dynamic-import。安装此插件并且在.babelrc中配置:
{
  // 添加
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  
}

列如下面的实例:index.js

let btn = document.createElement('button');
btn.innerHTML = '点击加载视频';
btn.addEventListener('click',()=>{
    import(/* webpackChunkName: "video" */'./video').then(res=>{
        console.log(res.default);
    });
});
document.body.appendChild(btn);

webpack.config.js

module.exports = {
    // ...
    output:{
      chunkFilename:'[name].min.js'
    }
}

6、这样打包后的结果最终的文件就是 video.min.js,并且刚启动项目时不会加载该文件,只有当用户点击了按钮时才会动态加载该文件。

代码分割 code-slpit

1、根据入口文件构建分割代码。

  1. 单入口打包,构建的就是一个文件,。
module.exports = {
   enrty: './src/index.js',
   output: {
      filename: 'js/[name].js',
      path: path.resolve(__dirname, 'dist')
   }
}
  

2.多入口打包就会产生多个代码块

module.exports = {
   enrty: {
      index: './src/index.js',
      home: './src/home.js'
   },
   output: {
      filename: 'js/[name]-[hash:8].js',
      path: path.resolve(__dirname, 'dist')
   }
}
 

2、用webapck自带的 optimization 1.来吧node_modules 单独打包到一个chunk输出 2.自动分析多入口文件种有没有公共的文件,如果有会打包成一个单独的chunk 。

optimization: {
      splitChunks: {
            chunks: 'all'
      }
}

3、用js代码让文件单独打包成一个chunk 默认webapck会按文件的id打包生成文件名称,可以用 /webpackChunkName: 'abc'/ 修改生成的文件名。

import(/*webpackChunkName: 'abc'*/'xxx.js').then(() => {
      console.log('success')
}).catch(err){
      console.log('error')
}
posted @ 2020-12-04 16:52  boygdm  阅读(786)  评论(0编辑  收藏  举报