0
点赞
收藏
分享

微信扫一扫

不吹嘘,面试要会的Webpack小芝士(核心理论 < 实战代码)

英乐 2022-03-18 阅读 46

Webpack是什么

Webpack是一种前端资源构建工具,一个静态模块打包器(module bundle)
在Webpack看来,前端的所有资源文件(js/json/img/less/....)都会作为模块处理
它将根据模块的依赖进行静态分析,打包生成对应的静态资源

Webpack五个核心概念

1、Entry
入口提示Webpack一那个文件为入口起点开始打包,分析构建内部依赖图
2、output
输出指示Webpack打包后的资源bundles输出到哪里去,以及如何命名
3、loader
loader让Webpack能够去处理那些非js文件(webpack自身只能理解js)
4、plugins
插件可以用于执行范围更广的任务,插件的范围包括,从打包和压缩,一直到重新定义环境中的变量符
5、mode

webpack构建流程

确定入口:根据配置中的entry找到所有入口文件
编译模块:从入口文件开始,调用所有配置的loader对模块进行翻译,然后递归所有依赖的模块,重复编译,得到每个模块翻译后的最终内容以及它们之间的依赖关系。
输出资源:根据入口和模块的依赖关系,组装成一个个包含多个模块的chunk,然后将chunk转换成一个单独的文件加入输出列表
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统上。

有哪些常用的loader

image-loader:加载并且压缩图片文件

babel-loader:把ES6转换成ES5

css-loader:加载CSS,支持模块化,压缩,文件导入等特性;css-loader 是解释 `@import` 和 `url()`

style-loader:把CSS代码注入到JS中,通过DOM操作去加载CSS,最终是以style标签的形式嵌入到Html代码中

eslint-loader:通过ESLint检查JS代码

file-loader:把文件移动到输出文件夹中,在代码中通过相对URL区引用输出的文件

url-loader:和file-loader类似,通过设置limit能在文件很小的情况下以base64的方式把文件内容注入到代码中去,主要是用于处理css中的url()

html-loader:处理html中的图片,注意的是:url-loader使用的是es6解析模块,html-loader使用的是es5解析模块,需要将esModule:false

source-map-loader:加载额外的Source map文件,方便断点调试

postcss-loader:css兼容性处理,另外要搭配 postcss-preset-env

有哪些常见的Plugin?他们是解决什么问题的?

define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
MiniCssExtractPlugin:提取js中的css成单独文件  (注:需要再use中使用MiniCssExtractPlugin。loader)
HtmlWebpackPlugin:默认会一个空的HTML,自动引入打包输出的所有资源,通过配置template的文件路径,可以将文件内容进行复制
OptimizeCssAssetsWebpackPlugin:压缩css

压缩CSS

optimize-css-assets-webpack-plugin
用法:引入插件,使用插件new OptimizeCssAssetsWebpackPlugin()

压缩Html

plugins:[
        new HtmlWebpackPlugin({
            template:'./index.html',
            //压缩处理
            minify:{
                // 移除空格
                collapseWhitespace:true,
                // 移除注释
                removeComments:true
            }    
        }),

    ],

压缩JS

//为什么要使用 ParallelUglifyPlugin
//webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。

//当webpack有多个JS文件需要输出和压缩时候,原来会使用UglifyJS去一个个压缩并且输出,但是ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更加的提高。

npm i -D webpack-parallel-uglify-plugin
{
	plugins: [    
		new ParallelUglifyPlugin({
		  cacheDir: '.cache/',
		  uglifyJS:{
			output: {
			  comments: false
			},
			warnings: false,
			compress: {
			  drop_debugger: true,
			  drop_console: true
			}
		  }
		}),
	],
}

webpack-parallel-uglify-plugin配置参数说明
test: 使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/.
include: 使用正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
exclude: 使用正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
cacheDir: 缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
sourceMap:是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的
sourceMap发送给网站用户的浏览器。
uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。
uglifyES:用于压缩 ES6 代码时的配置,Object 类型,直接透传给 UglifyES 的参数。

eslint-loader用法(webpack<V5.0)

首先需要安装
npm i eslint eslint-loader eslint-plugin-import eslint-config-airbnb-base -D

//语法检查eslint-loader eslint,检测规则在package.json的eslintConfig中
            {
                test:/\.js$/,
                exclude:/node_modules/, //只检查自己的源代码,不检查别人的源代码
                loader:'eslint-loader',
                options:{
                    fix:true	//自动修复
                }
            }
//package.json中
"eslintConfig":{
    "extends":"airbnb-base"
  }

eslint-webpack-plugin用法

首先安装插件和eslint 
npm  i eslint eslint-webpack-plugin eslint-config-airbnb-base  eslint-plugin-import -D

const ESLintPlugin = require('eslint-webpack-plugin')
plugins:[
        new ESLintPlugin({
            extensions: ['js'],
            context: path.resolve(__dirname,'src'),
            exclude: '/node_modules',
            fix:true
        })
    ],
    
//在package.json中配置
"eslintConfig":{
    "extends":"airbnb-base"
}

JS兼容性处理:babel-loader用法

//安装 npm i babel-loader @babel/preset-env @babel/core
//1.但是只能转化基本语法,如promise不能被转化
//2.全部js兼容性处理-->@babel/polyfill   需要先安装@babel/polyfill  ,这样子会导致包太大了,因为做了所有兼容处理
//3.按需加载 --->corejs

//转换基本语法
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],	// 预设 指示babel做怎么样的兼容性处理
          plugins: ['@babel/plugin-proposal-object-rest-spread']
        }
      }
    }
  ]
}

//按需加载
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],	// 预设 指示babel做怎么样的兼容性处理
          plugins: [
              [
                  "@babel/preset-env",
                  {
                      //按需加载
                     useBuiltIns:'usage',
                      // 指定版本
                     corejs:{
                         version:3
                     },
                      targets:{
                          chrome:'60',
                          firefox:'60',
                          ie:'9',
                          safari:'10',
                          edge:'17'
                      }
                  }
              ]
              
          ]
        }
      }
    }
  ]
}

CSS兼容性处理:postcss

postcss-loader:用于处理css兼容性问题

1、首先需要安装 postcss-loader,postcss
2、在use中使用postcss-loader,同时需要配置postcss-preset-env,postcss-preset-env就是帮助postcss-loader在package.json中	寻找borwserslist

//package.json
"browserslist": {
      "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
      ],
      "production": [
          ">0.1%",
          "not dead",
          "not op_mini all"
      ]
},

method-1

// webpack.config.js
{
         test:/\.css$/,
         use:[
             MiniCssExtractPlugin.loader, 
             'css-loader',
             {
                 loader: 'postcss-loader',
                 options: {
                   postcssOptions: {
                     plugins: ["postcss-preset-env"]
                   }
                 }
              
               }
         ]
}

method-2

{
         test:/\.css$/,
         use:[
             MiniCssExtractPlugin.loader, 
             'css-loader',
             'postcss-loader'	//Loader 将会自动搜索配置文件。
         ]
}

//然后再 postcss.config.js中
module.exports = {
    plugins:[
        [
            'postcss-preset-env'
        ]
    ]
}

source-map追踪

作用:提供源代码到构建后代码的映射技术,如果构建代码出错,通过映射可以追踪源代码错误
source-map:外部,
	错误代码准确信息和源代码的错误位置
    
inline-source-map:内联(只生成一个source-map)
	错误代码准确信息和源代码的错误位置
    
eval-source-map:内联(每个文件都会生成一个source-map进行追踪)
	错误代码准确信息和源代码的错误位置
hidden-source-map:外部
	错误代码准确信息和源代码的错误位置
    不能追踪源代码错误,智能提示构建后代码的位置错误
    
nosources-source-map:外部
	错误代码准确信息,但是没有源代码的任何信息 
    
cheap-source-map:外部
	错误代码准确信息和源代码的错误位置
    只能精确到行

cheap-module-source-map:外部
	错误代码准确信息和源代码的错误位置
	module会将loader的source map加入

如何选择:
开发环境:速度快,调试友好
	
生产环境:源码是否要隐藏,调试要不要友好

webpack性能优化

开发环境:优化打包构建速度,优化代码调试,
生产环境:优化打包构建速度,优化代码运行的性能
1.减少不必要的模块依赖
module.noParse字段可用于配置不需要解析的模块。对于类似jquery和lodash这些大型的第三方库,本身就是兼容性非常好的,不需要在进行解析。可以忽略以提升构建速度。
module.exports = {
  // ...
  module: {
    noParse: /jquery|lodash/, // 正则表达式
  }
}

2.减少不必要的loader处理
使用loader的时候尽可能考虑不需要处理的模块,以提升构建速度。
通过module.rule.exclude或module.rule.include排除或仅包含需要应用loader的场景。
rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/, //不匹配
    loader: "eslint-loader",
  },
  {
    test: /\.js$/,
    include: /src/, //匹配
    loader: "babel-loader",
  },
]

3.通过配置区分生产环境和开发环境的plugin,不要混用,导致影响构建速度。

4.使用cache-loader进行缓存文件。
对于一些比较消耗性能的模块,并且打包后文件内容没有改动可以使用cache-loader进行缓存。因为缓存是直接保存在硬盘,所以存取缓存都是要消耗一定的性能。小文件没必要缓存。下次就可以不使用loader而使用缓存。
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'cache-loader',
            options: {
              cacheDirectory: path.resolve('.cache') //还能进行配置
            }
          },
          'babel-loader'
        ],
        include: path.resolve('src')
      }
    ]
  }
}

5.开启多线程构建
webpack是基于node.js运行的,而node.js是单线程的,所以webpack处理文件是一个个处理的,如果需要处理的文件一多的话,处理速度会很慢,因此在需要解析大量文件的项目中,可以通过开启多线程来处理文件,以加快构建速度。
如thread-loader会开启一个线程池,线程池中包含适量的线程,利用多线程同时开启多个loader的处理
module.exports = {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          "thread-loader",
          "babel-loader"
        ]
      }
    ]
  }
};
可以使用happypack是一个开启多线程解析和构建文件的plugin。
var HappyPack = require('happypack')
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
  mode: "development",
  plugins: [
  new HappyPack({
    id: 'js',
    loaders: ['babel-loader'],
    // 用于检索工作线程的预定义线程池
    threadPool: happyThreadPool,
    // 启用此选项可将状态消息从HappyPack记录到STDOUT
    verbose: true
  })
]


如何优化打包后的文件:
打包后的文件最优的结果就是文件尽可能少、文件尽可能小,所以只需要根据这两个方向去优化。

1.模块分离
模块分离就是将一个整体的代码,分布到不同的打包文件中去,目的就是为了减少公共代码,降低打包文件总体积,特别是对于一些大型的第三方库,使用.模块分离还能充分利用浏览器缓存去加载这些公共模块,不用每次都去请求,利于传输速度。当多个chunk引入了公共模块的时候,webpack默认是不分离模块的,这样就会造成代码重复,所以当多个chunk引入了公共模块或者公共模块体积比较大,或者公共模块较少变动的时候,可以使用分包。
分包有两种方式:手动分离和自动分离

手动分离
手动分包就是开发人员通过根据分析模块的依赖关系,把公共模块通过打包或手动放到一个公共文件夹里,被打包后的index.html文件引入到页面。
大致步骤:
(1)提取公共模块到公共文件夹,并生成资源清单列表(可以手动创建,也可以使用webpack内置插件DllPlugin生成)
(2)在html页面引入公共模块
(3)使用DllReferencePlugin控制chunk打包结果,资源清单里面有的模块,不在打包进入bundle里面。

自动分离
自动分离相对于手动分离会方便一点,只需要配置要分离策略。
webpack提供了optimization配置项,用于配置一些优化信息
其中splitChunks是分离模块策略的配置

module.exports = {
  optimization: {
    splitChunks: {
      // 分包策略
      chunks: 'all',
    }
  }
}

使用以上chunks为all,就可以满足大部分的场景了。

附上博客:
https://blog.csdn.net/qq_38888512/article/details/116203201?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163907556216780261972559%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163907556216780261972559&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-7-116203201.pc_search_result_control_group&utm_term=webpack%E6%80%8E%E4%B9%88%E4%BC%98%E5%8C%96&spm=1018.2226.3001.4187

优化开发环境打包构建速度-->HMR模块热替换

作用:一个模块发生变化,只会重新打包这一个模块,极大提升构建速度
1、css文件:可以使用HRM功能,因为style-loader内部实现了
2、js文件:默认不能使用HMR
3、html文件:默认不能使用HMR,解决方案:修改entry入口,将html添加到入口; entry:['./src/js/index.js','./src/index/html'],但其实html可以不用做HMR功能

//解决js不能热模块更替问题
import acss from '../css/a.css'
import index from '../css/index.css'
import bjs from './b'
bjs()
const a = (x, y) => x + y;
console.log(a(1, 2));
console.log(2);

if(module.hot){	//如果module.hot为true,说明开启了HMR功能
    module.hot.accept('./b.js',function(){//监听b.js变化,执行回调
        bjs()
    })
}

Tree shaking

作用:去除无用代码,减少打包体积
前提:必须使用ES6模块化,必须开启生产环境
在package.json中配置
"sideEffects":false  
	//表示所有代码都没有副作用(都可以进行tree shaking),但是会把css / @babel/polyfill文件干掉

"sideEffects":['*.css']  
	//不会进行tree shaking

或者在启动webpack时追加参数--optimize-minimize来实现

代码分割 code split

const path = require('path');

module.exports = {
    mode: 'production',
    entry: {
        index: './src/js/index.js',
        base: './src/js/base.js'
    },
    output: {
        filename: 'js/[name][contenthash:8].js',
        path: path.resolve(__dirname, 'bulid'),
    },
    optimization:{
    	//被注释的代码不是必须的,可以根据自己需求开启
        splitChunks:{
            chunks: 'all',
        },
         //将当前模块记录其他模块的hash单独打包为一个文件runtime
        runtimeChunk:{
			name: entrypoint => `runtime-${entrypoint.name}`
		},
    }
}

预加载和懒加载

懒加载:文件被需要时才加载
document.getElementById('btn').onclick = function () {
    // 懒加载
    import(/* webpackChunkName: 'test' */"./test.js")
        .then(({multi}) => {
            console.log(multi)
            console.log('multi=', multi(3, 3))
        }).catch(() => {
            console.log('test文件加载失败!')
        })
}

预加载(prefetch):在其他资源加载完毕后,再加载资源
document.getElementById('btn').onclick = function () {
    // 预加载
    import(/* webpackChunkName: 'test', webpackPrefetch: true */ "./test.js")
        .then(({multi, sum}) => {
            console.log(multi, sum)
            console.log('multi=', multi(3, 3))
        }).catch(() => {
            console.log('test文件加载失败!')
        })
}

PWA技术

渐进式网络开发应用进程(离线可访问)
插件:workbox--> workbox-webpack-plugin

new WorkboxWebpackPlugin({
            //1.帮助苏serviceworker快速启动 ;2.删除旧的serviceworker
            clientClaim:true,
            skipWaiting:true
        })

多进程打包

安装thread-loader
开启多进程打包,进程启动需要600ms左右,进程通信也有开销,只有打包工作比较长的才需要多进程打包
use:[
                    // 开启多进程打包
				{
                        loader:'thread-loader',
                        options:{
                            workers:2 //进程2
                        }
                    },
                    {
                        loader:'babel-loader',
                        options:{
                            // 预设 指示babel做怎么样的兼容性处理
                            presets:[
                                [
                                    "@babel/preset-env",
                                    {
                                        //按需加载
                                        useBuiltIns:'usage',
                                        // 指定版本
                                        corejs:{
                                            version:3
                                        },
                                        // 指定兼容性做到哪个版本的浏览器
                                        targets:{
                                            chrome:'60',
                                            firefox:'60',
                                            ie:'9',
                                            safari:'10',
                                            edge:'17'
                                        }
                                    }
                                ]
                            ]
                        }
                    }
                ]

externals

//打包的时候忽略jquery,同时要配置jquery的CDN 
externals:{
    jquery:"jQuery"
}
举报

相关推荐

0 条评论